dspx 1.1.0 ā 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/README.md +55 -15
- package/binding.gyp +2 -1
- package/dist/bindings.js +1 -1
- package/dist/bindings.js.map +1 -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 +4 -1
- package/prebuilds/darwin-arm64/dspx.node +0 -0
- package/prebuilds/win32-x64/dspx.node +0 -0
- package/scripts/install.js +98 -0
- package/scripts/postinstall-verify.js +32 -0
- package/scripts/test-install.js +82 -0
- package/scripts/test.js +24 -0
- package/src/native/DspPipeline.cc +38 -0
- package/src/native/adapters/FftStage.cc +441 -0
- package/src/native/adapters/FftStage.h +103 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test installation behavior
|
|
5
|
+
* Usage: node scripts/test-install.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { arch, platform } from "os";
|
|
9
|
+
import { existsSync, rmSync } from "fs";
|
|
10
|
+
import { execSync } from "child_process";
|
|
11
|
+
import { join, dirname } from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
const projectRoot = join(__dirname, "..");
|
|
17
|
+
|
|
18
|
+
console.log("\nTesting dspx installation behavior\n");
|
|
19
|
+
console.log(`Platform: ${platform()}`);
|
|
20
|
+
console.log(`Architecture: ${arch()}`);
|
|
21
|
+
|
|
22
|
+
const isArm = arch().includes("arm") || arch().includes("aarch");
|
|
23
|
+
const buildDir = join(projectRoot, "build");
|
|
24
|
+
const prebuildsDir = join(projectRoot, "prebuilds");
|
|
25
|
+
|
|
26
|
+
console.log(
|
|
27
|
+
`\nBuild directory: ${existsSync(buildDir) ? " exists" : " missing"}`
|
|
28
|
+
);
|
|
29
|
+
console.log(
|
|
30
|
+
`Prebuilds directory: ${existsSync(prebuildsDir) ? " exists" : " missing"}`
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
console.log("\n Test Options:");
|
|
34
|
+
console.log("1. Test with prebuilds (simulate x64 user)");
|
|
35
|
+
console.log("2. Test without prebuilds (force compilation)");
|
|
36
|
+
console.log("3. Test current state");
|
|
37
|
+
console.log("4. Clean all build artifacts");
|
|
38
|
+
|
|
39
|
+
// For simplicity, run test 3 (current state)
|
|
40
|
+
console.log("\n Running test: Current state\n");
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Try to load the module
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
|
|
46
|
+
console.log("Attempting to load native addon...");
|
|
47
|
+
|
|
48
|
+
execSync("node scripts/postinstall-verify.js", {
|
|
49
|
+
cwd: projectRoot,
|
|
50
|
+
stdio: "inherit",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const elapsed = Date.now() - startTime;
|
|
54
|
+
console.log(`\n Success! Loaded in ${elapsed}ms`);
|
|
55
|
+
|
|
56
|
+
if (isArm) {
|
|
57
|
+
console.log("\n ARM detected: Module was compiled locally");
|
|
58
|
+
} else {
|
|
59
|
+
if (existsSync(prebuildsDir)) {
|
|
60
|
+
console.log("\n x64 detected: Module loaded from prebuilds");
|
|
61
|
+
} else {
|
|
62
|
+
console.log(
|
|
63
|
+
"\nš” x64 detected: Module was compiled (no prebuilds available)"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error("\nFailed to load module");
|
|
69
|
+
console.error(error.message);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log("\nInstallation test complete!\n");
|
|
74
|
+
|
|
75
|
+
// Show next steps
|
|
76
|
+
console.log("Next steps:");
|
|
77
|
+
console.log(' - Run "npm test" to verify functionality');
|
|
78
|
+
console.log(" - Check PREBUILDS.md for prebuild generation");
|
|
79
|
+
if (!existsSync(prebuildsDir)) {
|
|
80
|
+
console.log(' - Run "npm run prebuildify" to generate prebuilds');
|
|
81
|
+
}
|
|
82
|
+
console.log("");
|
package/scripts/test.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { dirname } from "path";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const testDir = join(__dirname, "../src/ts/__tests__");
|
|
9
|
+
|
|
10
|
+
// Get all test files
|
|
11
|
+
const testFiles = readdirSync(testDir)
|
|
12
|
+
.filter((file) => file.endsWith(".test.ts"))
|
|
13
|
+
.map((file) => join(testDir, file));
|
|
14
|
+
|
|
15
|
+
// Run node test runner with all test files
|
|
16
|
+
const args = ["--import", "tsx", "--test", ...testFiles];
|
|
17
|
+
const child = spawn("node", args, {
|
|
18
|
+
stdio: "inherit",
|
|
19
|
+
shell: true,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
child.on("exit", (code) => {
|
|
23
|
+
process.exit(code);
|
|
24
|
+
});
|
|
@@ -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
|