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.
@@ -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("");
@@ -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 &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