dspx 0.1.1-alpha.0
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/.github/workflows/ci.yml +185 -0
- package/.vscode/c_cpp_properties.json +17 -0
- package/.vscode/settings.json +68 -0
- package/.vscode/tasks.json +28 -0
- package/DISCLAIMER.md +32 -0
- package/LICENSE +21 -0
- package/README.md +1803 -0
- package/ROADMAP.md +192 -0
- package/TECHNICAL_DEBT.md +165 -0
- package/binding.gyp +65 -0
- package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
- package/docs/AUTHENTICATION_SECURITY.md +396 -0
- package/docs/BACKEND_IMPROVEMENTS.md +399 -0
- package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
- package/docs/FFT_IMPLEMENTATION.md +490 -0
- package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
- package/docs/FFT_USER_GUIDE.md +494 -0
- package/docs/FILTERS_IMPLEMENTATION.md +260 -0
- package/docs/FILTER_API_GUIDE.md +418 -0
- package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
- package/docs/LOGGER_API_REFERENCE.md +350 -0
- package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
- package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
- package/docs/PHASES_5_7_SUMMARY.md +403 -0
- package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
- package/docs/SIMD_OPTIMIZATIONS.md +211 -0
- package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
- package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
- package/docs/TIMESERIES_QUICK_REF.md +85 -0
- package/docs/advanced.md +559 -0
- package/docs/time-series-guide.md +617 -0
- package/docs/time-series-migration.md +376 -0
- package/jest.config.js +37 -0
- package/package.json +42 -0
- package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
- package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
- package/scripts/test.js +24 -0
- package/src/build/dsp-ts-redis.node +0 -0
- package/src/native/DspPipeline.cc +675 -0
- package/src/native/DspPipeline.h +44 -0
- package/src/native/FftBindings.cc +817 -0
- package/src/native/FilterBindings.cc +1001 -0
- package/src/native/IDspStage.h +53 -0
- package/src/native/adapters/InterpolatorStage.h +201 -0
- package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
- package/src/native/adapters/MovingAverageStage.h +306 -0
- package/src/native/adapters/RectifyStage.h +88 -0
- package/src/native/adapters/ResamplerStage.h +238 -0
- package/src/native/adapters/RmsStage.h +299 -0
- package/src/native/adapters/SscStage.h +121 -0
- package/src/native/adapters/VarianceStage.h +307 -0
- package/src/native/adapters/WampStage.h +114 -0
- package/src/native/adapters/WaveformLengthStage.h +115 -0
- package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
- package/src/native/core/FftEngine.cc +441 -0
- package/src/native/core/FftEngine.h +224 -0
- package/src/native/core/FirFilter.cc +324 -0
- package/src/native/core/FirFilter.h +149 -0
- package/src/native/core/IirFilter.cc +576 -0
- package/src/native/core/IirFilter.h +210 -0
- package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
- package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
- package/src/native/core/MovingAverageFilter.cc +18 -0
- package/src/native/core/MovingAverageFilter.h +135 -0
- package/src/native/core/MovingFftFilter.cc +291 -0
- package/src/native/core/MovingFftFilter.h +203 -0
- package/src/native/core/MovingVarianceFilter.cc +194 -0
- package/src/native/core/MovingVarianceFilter.h +114 -0
- package/src/native/core/MovingZScoreFilter.cc +215 -0
- package/src/native/core/MovingZScoreFilter.h +113 -0
- package/src/native/core/Policies.h +352 -0
- package/src/native/core/RmsFilter.cc +18 -0
- package/src/native/core/RmsFilter.h +131 -0
- package/src/native/core/SscFilter.cc +16 -0
- package/src/native/core/SscFilter.h +137 -0
- package/src/native/core/WampFilter.cc +16 -0
- package/src/native/core/WampFilter.h +101 -0
- package/src/native/core/WaveformLengthFilter.cc +17 -0
- package/src/native/core/WaveformLengthFilter.h +98 -0
- package/src/native/utils/CircularBufferArray.cc +336 -0
- package/src/native/utils/CircularBufferArray.h +62 -0
- package/src/native/utils/CircularBufferVector.cc +145 -0
- package/src/native/utils/CircularBufferVector.h +45 -0
- package/src/native/utils/NapiUtils.cc +53 -0
- package/src/native/utils/NapiUtils.h +21 -0
- package/src/native/utils/SimdOps.h +870 -0
- package/src/native/utils/SlidingWindowFilter.cc +239 -0
- package/src/native/utils/SlidingWindowFilter.h +159 -0
- package/src/native/utils/TimeSeriesBuffer.cc +205 -0
- package/src/native/utils/TimeSeriesBuffer.h +140 -0
- package/src/ts/CircularLogBuffer.ts +87 -0
- package/src/ts/DriftDetector.ts +331 -0
- package/src/ts/TopicRouter.ts +428 -0
- package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
- package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
- package/src/ts/__tests__/Chaining.test.ts +387 -0
- package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
- package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
- package/src/ts/__tests__/DriftDetector.test.ts +389 -0
- package/src/ts/__tests__/Fft.test.ts +484 -0
- package/src/ts/__tests__/ListState.test.ts +153 -0
- package/src/ts/__tests__/Logger.test.ts +208 -0
- package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
- package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
- package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
- package/src/ts/__tests__/MovingAverage.test.ts +322 -0
- package/src/ts/__tests__/RMS.test.ts +315 -0
- package/src/ts/__tests__/Rectify.test.ts +272 -0
- package/src/ts/__tests__/Redis.test.ts +456 -0
- package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
- package/src/ts/__tests__/Tap.test.ts +164 -0
- package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
- package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
- package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
- package/src/ts/__tests__/TimeSeries.test.ts +254 -0
- package/src/ts/__tests__/TopicRouter.test.ts +332 -0
- package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
- package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
- package/src/ts/__tests__/Variance.test.ts +509 -0
- package/src/ts/__tests__/WaveformLength.test.ts +147 -0
- package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
- package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
- package/src/ts/advanced-dsp.ts +566 -0
- package/src/ts/backends.ts +1137 -0
- package/src/ts/bindings.ts +1225 -0
- package/src/ts/easter-egg.ts +42 -0
- package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
- package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
- package/src/ts/examples/MovingAverage/test-state.ts +85 -0
- package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
- package/src/ts/examples/RMS/test-state.ts +97 -0
- package/src/ts/examples/RMS/test-streaming.ts +253 -0
- package/src/ts/examples/Rectify/test-state.ts +107 -0
- package/src/ts/examples/Rectify/test-streaming.ts +242 -0
- package/src/ts/examples/Variance/test-state.ts +195 -0
- package/src/ts/examples/Variance/test-streaming.ts +260 -0
- package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
- package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
- package/src/ts/examples/advanced-dsp-examples.ts +397 -0
- package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
- package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
- package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
- package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
- package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
- package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
- package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
- package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
- package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
- package/src/ts/examples/chaining/test-chaining.ts +52 -0
- package/src/ts/examples/emg-features-example.ts +284 -0
- package/src/ts/examples/fft-example.ts +309 -0
- package/src/ts/examples/fft-examples.ts +349 -0
- package/src/ts/examples/filter-examples.ts +320 -0
- package/src/ts/examples/list-state-example.ts +131 -0
- package/src/ts/examples/logger-example.ts +91 -0
- package/src/ts/examples/notch-filter-examples.ts +243 -0
- package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
- package/src/ts/examples/phase6-7/production-observability.ts +476 -0
- package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
- package/src/ts/examples/redis/redis-example.ts +202 -0
- package/src/ts/examples/redis-example.ts +202 -0
- package/src/ts/examples/simd-benchmark.ts +126 -0
- package/src/ts/examples/tap-debugging.ts +230 -0
- package/src/ts/examples/timeseries/comparison-example.ts +290 -0
- package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
- package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
- package/src/ts/examples/waveform-length-example.ts +139 -0
- package/src/ts/fft.ts +722 -0
- package/src/ts/filters.ts +1078 -0
- package/src/ts/index.ts +120 -0
- package/src/ts/types.ts +589 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* N-API Bindings for FFT/DFT Engine
|
|
3
|
+
*
|
|
4
|
+
* Exposes all 8 transforms to TypeScript:
|
|
5
|
+
* - fft, ifft, dft, idft (complex)
|
|
6
|
+
* - rfft, irfft, rdft, irdft (real)
|
|
7
|
+
*
|
|
8
|
+
* Plus moving/batched FFT processing
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include <napi.h>
|
|
12
|
+
#include "core/FftEngine.h"
|
|
13
|
+
#include "core/MovingFftFilter.h"
|
|
14
|
+
#include "utils/NapiUtils.h"
|
|
15
|
+
#include <memory>
|
|
16
|
+
|
|
17
|
+
namespace dsp
|
|
18
|
+
{
|
|
19
|
+
|
|
20
|
+
using Complex = std::complex<float>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* FftProcessor - Wraps FftEngine for TypeScript
|
|
24
|
+
*/
|
|
25
|
+
class FftProcessor : public Napi::ObjectWrap<FftProcessor>
|
|
26
|
+
{
|
|
27
|
+
public:
|
|
28
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
29
|
+
FftProcessor(const Napi::CallbackInfo &info);
|
|
30
|
+
|
|
31
|
+
private:
|
|
32
|
+
std::unique_ptr<core::FftEngine<float>> m_engine;
|
|
33
|
+
size_t m_size;
|
|
34
|
+
|
|
35
|
+
// TypeScript methods
|
|
36
|
+
Napi::Value Fft(const Napi::CallbackInfo &info);
|
|
37
|
+
Napi::Value Ifft(const Napi::CallbackInfo &info);
|
|
38
|
+
Napi::Value Dft(const Napi::CallbackInfo &info);
|
|
39
|
+
Napi::Value Idft(const Napi::CallbackInfo &info);
|
|
40
|
+
Napi::Value Rfft(const Napi::CallbackInfo &info);
|
|
41
|
+
Napi::Value Irfft(const Napi::CallbackInfo &info);
|
|
42
|
+
Napi::Value Rdft(const Napi::CallbackInfo &info);
|
|
43
|
+
Napi::Value Irdft(const Napi::CallbackInfo &info);
|
|
44
|
+
|
|
45
|
+
Napi::Value GetSize(const Napi::CallbackInfo &info);
|
|
46
|
+
Napi::Value GetHalfSize(const Napi::CallbackInfo &info);
|
|
47
|
+
Napi::Value IsPowerOfTwo(const Napi::CallbackInfo &info);
|
|
48
|
+
|
|
49
|
+
Napi::Value GetMagnitude(const Napi::CallbackInfo &info);
|
|
50
|
+
Napi::Value GetPhase(const Napi::CallbackInfo &info);
|
|
51
|
+
Napi::Value GetPower(const Napi::CallbackInfo &info);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* MovingFftProcessor - Wraps MovingFftFilter for TypeScript
|
|
56
|
+
*/
|
|
57
|
+
class MovingFftProcessor : public Napi::ObjectWrap<MovingFftProcessor>
|
|
58
|
+
{
|
|
59
|
+
public:
|
|
60
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
61
|
+
MovingFftProcessor(const Napi::CallbackInfo &info);
|
|
62
|
+
|
|
63
|
+
private:
|
|
64
|
+
std::unique_ptr<core::MovingFftFilter<float>> m_filter;
|
|
65
|
+
|
|
66
|
+
Napi::Value AddSample(const Napi::CallbackInfo &info);
|
|
67
|
+
Napi::Value AddSamples(const Napi::CallbackInfo &info);
|
|
68
|
+
Napi::Value ComputeSpectrum(const Napi::CallbackInfo &info);
|
|
69
|
+
Napi::Value Reset(const Napi::CallbackInfo &info);
|
|
70
|
+
|
|
71
|
+
Napi::Value GetFftSize(const Napi::CallbackInfo &info);
|
|
72
|
+
Napi::Value GetSpectrumSize(const Napi::CallbackInfo &info);
|
|
73
|
+
Napi::Value GetHopSize(const Napi::CallbackInfo &info);
|
|
74
|
+
Napi::Value GetFillLevel(const Napi::CallbackInfo &info);
|
|
75
|
+
Napi::Value IsReady(const Napi::CallbackInfo &info);
|
|
76
|
+
|
|
77
|
+
Napi::Value SetWindowType(const Napi::CallbackInfo &info);
|
|
78
|
+
Napi::Value GetMagnitudeSpectrum(const Napi::CallbackInfo &info);
|
|
79
|
+
Napi::Value GetPowerSpectrum(const Napi::CallbackInfo &info);
|
|
80
|
+
Napi::Value GetPhaseSpectrum(const Napi::CallbackInfo &info);
|
|
81
|
+
Napi::Value GetFrequencyBins(const Napi::CallbackInfo &info);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ========== FftProcessor Implementation ==========
|
|
85
|
+
|
|
86
|
+
Napi::Object FftProcessor::Init(Napi::Env env, Napi::Object exports)
|
|
87
|
+
{
|
|
88
|
+
Napi::Function func = DefineClass(env, "FftProcessor", {
|
|
89
|
+
InstanceMethod("fft", &FftProcessor::Fft),
|
|
90
|
+
InstanceMethod("ifft", &FftProcessor::Ifft),
|
|
91
|
+
InstanceMethod("dft", &FftProcessor::Dft),
|
|
92
|
+
InstanceMethod("idft", &FftProcessor::Idft),
|
|
93
|
+
InstanceMethod("rfft", &FftProcessor::Rfft),
|
|
94
|
+
InstanceMethod("irfft", &FftProcessor::Irfft),
|
|
95
|
+
InstanceMethod("rdft", &FftProcessor::Rdft),
|
|
96
|
+
InstanceMethod("irdft", &FftProcessor::Irdft),
|
|
97
|
+
|
|
98
|
+
InstanceMethod("getSize", &FftProcessor::GetSize),
|
|
99
|
+
InstanceMethod("getHalfSize", &FftProcessor::GetHalfSize),
|
|
100
|
+
InstanceMethod("isPowerOfTwo", &FftProcessor::IsPowerOfTwo),
|
|
101
|
+
|
|
102
|
+
InstanceMethod("getMagnitude", &FftProcessor::GetMagnitude),
|
|
103
|
+
InstanceMethod("getPhase", &FftProcessor::GetPhase),
|
|
104
|
+
InstanceMethod("getPower", &FftProcessor::GetPower),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
Napi::FunctionReference *constructor = new Napi::FunctionReference();
|
|
108
|
+
*constructor = Napi::Persistent(func);
|
|
109
|
+
env.SetInstanceData(constructor);
|
|
110
|
+
|
|
111
|
+
exports.Set("FftProcessor", func);
|
|
112
|
+
return exports;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
FftProcessor::FftProcessor(const Napi::CallbackInfo &info)
|
|
116
|
+
: Napi::ObjectWrap<FftProcessor>(info)
|
|
117
|
+
{
|
|
118
|
+
Napi::Env env = info.Env();
|
|
119
|
+
|
|
120
|
+
if (info.Length() < 1 || !info[0].IsNumber())
|
|
121
|
+
{
|
|
122
|
+
Napi::TypeError::New(env, "Expected FFT size (number)").ThrowAsJavaScriptException();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
m_size = info[0].As<Napi::Number>().Uint32Value();
|
|
127
|
+
m_engine = std::make_unique<core::FftEngine<float>>(m_size);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
Napi::Value FftProcessor::Fft(const Napi::CallbackInfo &info)
|
|
131
|
+
{
|
|
132
|
+
Napi::Env env = info.Env();
|
|
133
|
+
|
|
134
|
+
// Expect: { real: Float32Array, imag: Float32Array }
|
|
135
|
+
if (info.Length() < 1 || !info[0].IsObject())
|
|
136
|
+
{
|
|
137
|
+
Napi::TypeError::New(env, "Expected complex input { real, imag }").ThrowAsJavaScriptException();
|
|
138
|
+
return env.Null();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Napi::Object inputObj = info[0].As<Napi::Object>();
|
|
142
|
+
Napi::Float32Array realIn = inputObj.Get("real").As<Napi::Float32Array>();
|
|
143
|
+
Napi::Float32Array imagIn = inputObj.Get("imag").As<Napi::Float32Array>();
|
|
144
|
+
|
|
145
|
+
if (realIn.ElementLength() != m_size || imagIn.ElementLength() != m_size)
|
|
146
|
+
{
|
|
147
|
+
Napi::TypeError::New(env, "Input arrays must match FFT size").ThrowAsJavaScriptException();
|
|
148
|
+
return env.Null();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Pack into complex array
|
|
152
|
+
std::vector<Complex> input(m_size);
|
|
153
|
+
std::vector<Complex> output(m_size);
|
|
154
|
+
|
|
155
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
156
|
+
{
|
|
157
|
+
input[i] = Complex(realIn[i], imagIn[i]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Compute FFT
|
|
161
|
+
m_engine->fft(input.data(), output.data());
|
|
162
|
+
|
|
163
|
+
// Unpack result
|
|
164
|
+
Napi::Float32Array realOut = Napi::Float32Array::New(env, m_size);
|
|
165
|
+
Napi::Float32Array imagOut = Napi::Float32Array::New(env, m_size);
|
|
166
|
+
|
|
167
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
168
|
+
{
|
|
169
|
+
realOut[i] = output[i].real();
|
|
170
|
+
imagOut[i] = output[i].imag();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
Napi::Object result = Napi::Object::New(env);
|
|
174
|
+
result.Set("real", realOut);
|
|
175
|
+
result.Set("imag", imagOut);
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
Napi::Value FftProcessor::Ifft(const Napi::CallbackInfo &info)
|
|
181
|
+
{
|
|
182
|
+
Napi::Env env = info.Env();
|
|
183
|
+
|
|
184
|
+
Napi::Object inputObj = info[0].As<Napi::Object>();
|
|
185
|
+
Napi::Float32Array realIn = inputObj.Get("real").As<Napi::Float32Array>();
|
|
186
|
+
Napi::Float32Array imagIn = inputObj.Get("imag").As<Napi::Float32Array>();
|
|
187
|
+
|
|
188
|
+
std::vector<Complex> input(m_size);
|
|
189
|
+
std::vector<Complex> output(m_size);
|
|
190
|
+
|
|
191
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
192
|
+
{
|
|
193
|
+
input[i] = Complex(realIn[i], imagIn[i]);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
m_engine->ifft(input.data(), output.data());
|
|
197
|
+
|
|
198
|
+
Napi::Float32Array realOut = Napi::Float32Array::New(env, m_size);
|
|
199
|
+
Napi::Float32Array imagOut = Napi::Float32Array::New(env, m_size);
|
|
200
|
+
|
|
201
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
202
|
+
{
|
|
203
|
+
realOut[i] = output[i].real();
|
|
204
|
+
imagOut[i] = output[i].imag();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
Napi::Object result = Napi::Object::New(env);
|
|
208
|
+
result.Set("real", realOut);
|
|
209
|
+
result.Set("imag", imagOut);
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Napi::Value FftProcessor::Dft(const Napi::CallbackInfo &info)
|
|
215
|
+
{
|
|
216
|
+
// Same as FFT but uses DFT algorithm
|
|
217
|
+
Napi::Env env = info.Env();
|
|
218
|
+
|
|
219
|
+
Napi::Object inputObj = info[0].As<Napi::Object>();
|
|
220
|
+
Napi::Float32Array realIn = inputObj.Get("real").As<Napi::Float32Array>();
|
|
221
|
+
Napi::Float32Array imagIn = inputObj.Get("imag").As<Napi::Float32Array>();
|
|
222
|
+
|
|
223
|
+
std::vector<Complex> input(m_size);
|
|
224
|
+
std::vector<Complex> output(m_size);
|
|
225
|
+
|
|
226
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
227
|
+
{
|
|
228
|
+
input[i] = Complex(realIn[i], imagIn[i]);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
m_engine->dft(input.data(), output.data());
|
|
232
|
+
|
|
233
|
+
Napi::Float32Array realOut = Napi::Float32Array::New(env, m_size);
|
|
234
|
+
Napi::Float32Array imagOut = Napi::Float32Array::New(env, m_size);
|
|
235
|
+
|
|
236
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
237
|
+
{
|
|
238
|
+
realOut[i] = output[i].real();
|
|
239
|
+
imagOut[i] = output[i].imag();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
Napi::Object result = Napi::Object::New(env);
|
|
243
|
+
result.Set("real", realOut);
|
|
244
|
+
result.Set("imag", imagOut);
|
|
245
|
+
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
Napi::Value FftProcessor::Idft(const Napi::CallbackInfo &info)
|
|
250
|
+
{
|
|
251
|
+
Napi::Env env = info.Env();
|
|
252
|
+
|
|
253
|
+
Napi::Object inputObj = info[0].As<Napi::Object>();
|
|
254
|
+
Napi::Float32Array realIn = inputObj.Get("real").As<Napi::Float32Array>();
|
|
255
|
+
Napi::Float32Array imagIn = inputObj.Get("imag").As<Napi::Float32Array>();
|
|
256
|
+
|
|
257
|
+
std::vector<Complex> input(m_size);
|
|
258
|
+
std::vector<Complex> output(m_size);
|
|
259
|
+
|
|
260
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
261
|
+
{
|
|
262
|
+
input[i] = Complex(realIn[i], imagIn[i]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
m_engine->idft(input.data(), output.data());
|
|
266
|
+
|
|
267
|
+
Napi::Float32Array realOut = Napi::Float32Array::New(env, m_size);
|
|
268
|
+
Napi::Float32Array imagOut = Napi::Float32Array::New(env, m_size);
|
|
269
|
+
|
|
270
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
271
|
+
{
|
|
272
|
+
realOut[i] = output[i].real();
|
|
273
|
+
imagOut[i] = output[i].imag();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
Napi::Object result = Napi::Object::New(env);
|
|
277
|
+
result.Set("real", realOut);
|
|
278
|
+
result.Set("imag", imagOut);
|
|
279
|
+
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
Napi::Value FftProcessor::Rfft(const Napi::CallbackInfo &info)
|
|
284
|
+
{
|
|
285
|
+
Napi::Env env = info.Env();
|
|
286
|
+
|
|
287
|
+
// Expect: Float32Array (real input only)
|
|
288
|
+
if (info.Length() < 1 || !info[0].IsTypedArray())
|
|
289
|
+
{
|
|
290
|
+
Napi::TypeError::New(env, "Expected Float32Array").ThrowAsJavaScriptException();
|
|
291
|
+
return env.Null();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
Napi::Float32Array input = info[0].As<Napi::Float32Array>();
|
|
295
|
+
|
|
296
|
+
if (input.ElementLength() != m_size)
|
|
297
|
+
{
|
|
298
|
+
Napi::TypeError::New(env, "Input must match FFT size").ThrowAsJavaScriptException();
|
|
299
|
+
return env.Null();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
size_t halfSize = m_engine->getHalfSize();
|
|
303
|
+
std::vector<Complex> output(halfSize);
|
|
304
|
+
|
|
305
|
+
m_engine->rfft(input.Data(), output.data());
|
|
306
|
+
|
|
307
|
+
Napi::Float32Array realOut = Napi::Float32Array::New(env, halfSize);
|
|
308
|
+
Napi::Float32Array imagOut = Napi::Float32Array::New(env, halfSize);
|
|
309
|
+
|
|
310
|
+
for (size_t i = 0; i < halfSize; ++i)
|
|
311
|
+
{
|
|
312
|
+
realOut[i] = output[i].real();
|
|
313
|
+
imagOut[i] = output[i].imag();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
Napi::Object result = Napi::Object::New(env);
|
|
317
|
+
result.Set("real", realOut);
|
|
318
|
+
result.Set("imag", imagOut);
|
|
319
|
+
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
Napi::Value FftProcessor::Irfft(const Napi::CallbackInfo &info)
|
|
324
|
+
{
|
|
325
|
+
Napi::Env env = info.Env();
|
|
326
|
+
|
|
327
|
+
Napi::Object inputObj = info[0].As<Napi::Object>();
|
|
328
|
+
Napi::Float32Array realIn = inputObj.Get("real").As<Napi::Float32Array>();
|
|
329
|
+
Napi::Float32Array imagIn = inputObj.Get("imag").As<Napi::Float32Array>();
|
|
330
|
+
|
|
331
|
+
size_t halfSize = m_engine->getHalfSize();
|
|
332
|
+
std::vector<Complex> input(halfSize);
|
|
333
|
+
std::vector<float> output(m_size);
|
|
334
|
+
|
|
335
|
+
for (size_t i = 0; i < halfSize; ++i)
|
|
336
|
+
{
|
|
337
|
+
input[i] = Complex(realIn[i], imagIn[i]);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
m_engine->irfft(input.data(), output.data());
|
|
341
|
+
|
|
342
|
+
Napi::Float32Array result = Napi::Float32Array::New(env, m_size);
|
|
343
|
+
std::copy(output.begin(), output.end(), result.Data());
|
|
344
|
+
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
Napi::Value FftProcessor::Rdft(const Napi::CallbackInfo &info)
|
|
349
|
+
{
|
|
350
|
+
// Same as RFFT but uses RDFT algorithm
|
|
351
|
+
Napi::Env env = info.Env();
|
|
352
|
+
|
|
353
|
+
Napi::Float32Array input = info[0].As<Napi::Float32Array>();
|
|
354
|
+
|
|
355
|
+
size_t halfSize = m_engine->getHalfSize();
|
|
356
|
+
std::vector<Complex> output(halfSize);
|
|
357
|
+
|
|
358
|
+
m_engine->rdft(input.Data(), output.data());
|
|
359
|
+
|
|
360
|
+
Napi::Float32Array realOut = Napi::Float32Array::New(env, halfSize);
|
|
361
|
+
Napi::Float32Array imagOut = Napi::Float32Array::New(env, halfSize);
|
|
362
|
+
|
|
363
|
+
for (size_t i = 0; i < halfSize; ++i)
|
|
364
|
+
{
|
|
365
|
+
realOut[i] = output[i].real();
|
|
366
|
+
imagOut[i] = output[i].imag();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
Napi::Object result = Napi::Object::New(env);
|
|
370
|
+
result.Set("real", realOut);
|
|
371
|
+
result.Set("imag", imagOut);
|
|
372
|
+
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
Napi::Value FftProcessor::Irdft(const Napi::CallbackInfo &info)
|
|
377
|
+
{
|
|
378
|
+
Napi::Env env = info.Env();
|
|
379
|
+
|
|
380
|
+
Napi::Object inputObj = info[0].As<Napi::Object>();
|
|
381
|
+
Napi::Float32Array realIn = inputObj.Get("real").As<Napi::Float32Array>();
|
|
382
|
+
Napi::Float32Array imagIn = inputObj.Get("imag").As<Napi::Float32Array>();
|
|
383
|
+
|
|
384
|
+
size_t halfSize = m_engine->getHalfSize();
|
|
385
|
+
std::vector<Complex> input(halfSize);
|
|
386
|
+
std::vector<float> output(m_size);
|
|
387
|
+
|
|
388
|
+
for (size_t i = 0; i < halfSize; ++i)
|
|
389
|
+
{
|
|
390
|
+
input[i] = Complex(realIn[i], imagIn[i]);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
m_engine->irdft(input.data(), output.data());
|
|
394
|
+
|
|
395
|
+
Napi::Float32Array result = Napi::Float32Array::New(env, m_size);
|
|
396
|
+
std::copy(output.begin(), output.end(), result.Data());
|
|
397
|
+
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
Napi::Value FftProcessor::GetSize(const Napi::CallbackInfo &info)
|
|
402
|
+
{
|
|
403
|
+
return Napi::Number::New(info.Env(), m_size);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
Napi::Value FftProcessor::GetHalfSize(const Napi::CallbackInfo &info)
|
|
407
|
+
{
|
|
408
|
+
return Napi::Number::New(info.Env(), m_engine->getHalfSize());
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
Napi::Value FftProcessor::IsPowerOfTwo(const Napi::CallbackInfo &info)
|
|
412
|
+
{
|
|
413
|
+
return Napi::Boolean::New(info.Env(), m_engine->isPowerOfTwo());
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
Napi::Value FftProcessor::GetMagnitude(const Napi::CallbackInfo &info)
|
|
417
|
+
{
|
|
418
|
+
Napi::Env env = info.Env();
|
|
419
|
+
|
|
420
|
+
Napi::Object spectrumObj = info[0].As<Napi::Object>();
|
|
421
|
+
Napi::Float32Array realIn = spectrumObj.Get("real").As<Napi::Float32Array>();
|
|
422
|
+
Napi::Float32Array imagIn = spectrumObj.Get("imag").As<Napi::Float32Array>();
|
|
423
|
+
|
|
424
|
+
size_t length = realIn.ElementLength();
|
|
425
|
+
std::vector<Complex> spectrum(length);
|
|
426
|
+
std::vector<float> magnitudes(length);
|
|
427
|
+
|
|
428
|
+
for (size_t i = 0; i < length; ++i)
|
|
429
|
+
{
|
|
430
|
+
spectrum[i] = Complex(realIn[i], imagIn[i]);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
m_engine->getMagnitude(spectrum.data(), magnitudes.data(), length);
|
|
434
|
+
|
|
435
|
+
Napi::Float32Array result = Napi::Float32Array::New(env, length);
|
|
436
|
+
std::copy(magnitudes.begin(), magnitudes.end(), result.Data());
|
|
437
|
+
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
Napi::Value FftProcessor::GetPhase(const Napi::CallbackInfo &info)
|
|
442
|
+
{
|
|
443
|
+
Napi::Env env = info.Env();
|
|
444
|
+
|
|
445
|
+
Napi::Object spectrumObj = info[0].As<Napi::Object>();
|
|
446
|
+
Napi::Float32Array realIn = spectrumObj.Get("real").As<Napi::Float32Array>();
|
|
447
|
+
Napi::Float32Array imagIn = spectrumObj.Get("imag").As<Napi::Float32Array>();
|
|
448
|
+
|
|
449
|
+
size_t length = realIn.ElementLength();
|
|
450
|
+
std::vector<Complex> spectrum(length);
|
|
451
|
+
std::vector<float> phases(length);
|
|
452
|
+
|
|
453
|
+
for (size_t i = 0; i < length; ++i)
|
|
454
|
+
{
|
|
455
|
+
spectrum[i] = Complex(realIn[i], imagIn[i]);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
m_engine->getPhase(spectrum.data(), phases.data(), length);
|
|
459
|
+
|
|
460
|
+
Napi::Float32Array result = Napi::Float32Array::New(env, length);
|
|
461
|
+
std::copy(phases.begin(), phases.end(), result.Data());
|
|
462
|
+
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
Napi::Value FftProcessor::GetPower(const Napi::CallbackInfo &info)
|
|
467
|
+
{
|
|
468
|
+
Napi::Env env = info.Env();
|
|
469
|
+
|
|
470
|
+
Napi::Object spectrumObj = info[0].As<Napi::Object>();
|
|
471
|
+
Napi::Float32Array realIn = spectrumObj.Get("real").As<Napi::Float32Array>();
|
|
472
|
+
Napi::Float32Array imagIn = spectrumObj.Get("imag").As<Napi::Float32Array>();
|
|
473
|
+
|
|
474
|
+
size_t length = realIn.ElementLength();
|
|
475
|
+
std::vector<Complex> spectrum(length);
|
|
476
|
+
std::vector<float> power(length);
|
|
477
|
+
|
|
478
|
+
for (size_t i = 0; i < length; ++i)
|
|
479
|
+
{
|
|
480
|
+
spectrum[i] = Complex(realIn[i], imagIn[i]);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
m_engine->getPower(spectrum.data(), power.data(), length);
|
|
484
|
+
|
|
485
|
+
Napi::Float32Array result = Napi::Float32Array::New(env, length);
|
|
486
|
+
std::copy(power.begin(), power.end(), result.Data());
|
|
487
|
+
|
|
488
|
+
return result;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ========== MovingFftProcessor Implementation ==========
|
|
492
|
+
|
|
493
|
+
Napi::Object MovingFftProcessor::Init(Napi::Env env, Napi::Object exports)
|
|
494
|
+
{
|
|
495
|
+
Napi::Function func = DefineClass(env, "MovingFftProcessor", {
|
|
496
|
+
InstanceMethod("addSample", &MovingFftProcessor::AddSample),
|
|
497
|
+
InstanceMethod("addSamples", &MovingFftProcessor::AddSamples),
|
|
498
|
+
InstanceMethod("computeSpectrum", &MovingFftProcessor::ComputeSpectrum),
|
|
499
|
+
InstanceMethod("reset", &MovingFftProcessor::Reset),
|
|
500
|
+
|
|
501
|
+
InstanceMethod("getFftSize", &MovingFftProcessor::GetFftSize),
|
|
502
|
+
InstanceMethod("getSpectrumSize", &MovingFftProcessor::GetSpectrumSize),
|
|
503
|
+
InstanceMethod("getHopSize", &MovingFftProcessor::GetHopSize),
|
|
504
|
+
InstanceMethod("getFillLevel", &MovingFftProcessor::GetFillLevel),
|
|
505
|
+
InstanceMethod("isReady", &MovingFftProcessor::IsReady),
|
|
506
|
+
|
|
507
|
+
InstanceMethod("setWindowType", &MovingFftProcessor::SetWindowType),
|
|
508
|
+
InstanceMethod("getMagnitudeSpectrum", &MovingFftProcessor::GetMagnitudeSpectrum),
|
|
509
|
+
InstanceMethod("getPowerSpectrum", &MovingFftProcessor::GetPowerSpectrum),
|
|
510
|
+
InstanceMethod("getPhaseSpectrum", &MovingFftProcessor::GetPhaseSpectrum),
|
|
511
|
+
InstanceMethod("getFrequencyBins", &MovingFftProcessor::GetFrequencyBins),
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
Napi::FunctionReference *constructor = new Napi::FunctionReference();
|
|
515
|
+
*constructor = Napi::Persistent(func);
|
|
516
|
+
exports.Set("MovingFftProcessor", func);
|
|
517
|
+
return exports;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
MovingFftProcessor::MovingFftProcessor(const Napi::CallbackInfo &info)
|
|
521
|
+
: Napi::ObjectWrap<MovingFftProcessor>(info)
|
|
522
|
+
{
|
|
523
|
+
Napi::Env env = info.Env();
|
|
524
|
+
|
|
525
|
+
// Expect: { fftSize, hopSize?, mode?, windowType?, realInput? }
|
|
526
|
+
if (info.Length() < 1 || !info[0].IsObject())
|
|
527
|
+
{
|
|
528
|
+
Napi::TypeError::New(env, "Expected options object").ThrowAsJavaScriptException();
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
Napi::Object options = info[0].As<Napi::Object>();
|
|
533
|
+
|
|
534
|
+
size_t fftSize = options.Get("fftSize").As<Napi::Number>().Uint32Value();
|
|
535
|
+
size_t hopSize = options.Has("hopSize") ? options.Get("hopSize").As<Napi::Number>().Uint32Value() : fftSize;
|
|
536
|
+
|
|
537
|
+
// Parse mode
|
|
538
|
+
core::FftMode mode = core::FftMode::Batched;
|
|
539
|
+
if (options.Has("mode"))
|
|
540
|
+
{
|
|
541
|
+
std::string modeStr = options.Get("mode").As<Napi::String>().Utf8Value();
|
|
542
|
+
if (modeStr == "moving")
|
|
543
|
+
{
|
|
544
|
+
mode = core::FftMode::Moving;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Parse window type
|
|
549
|
+
core::WindowType windowType = core::WindowType::Hann;
|
|
550
|
+
if (options.Has("windowType"))
|
|
551
|
+
{
|
|
552
|
+
std::string windowStr = options.Get("windowType").As<Napi::String>().Utf8Value();
|
|
553
|
+
if (windowStr == "none")
|
|
554
|
+
windowType = core::WindowType::None;
|
|
555
|
+
else if (windowStr == "hann")
|
|
556
|
+
windowType = core::WindowType::Hann;
|
|
557
|
+
else if (windowStr == "hamming")
|
|
558
|
+
windowType = core::WindowType::Hamming;
|
|
559
|
+
else if (windowStr == "blackman")
|
|
560
|
+
windowType = core::WindowType::Blackman;
|
|
561
|
+
else if (windowStr == "bartlett")
|
|
562
|
+
windowType = core::WindowType::Bartlett;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
bool realInput = options.Has("realInput") ? options.Get("realInput").As<Napi::Boolean>().Value() : true;
|
|
566
|
+
|
|
567
|
+
m_filter = std::make_unique<core::MovingFftFilter<float>>(
|
|
568
|
+
fftSize, hopSize, mode, windowType, realInput);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
Napi::Value MovingFftProcessor::AddSample(const Napi::CallbackInfo &info)
|
|
572
|
+
{
|
|
573
|
+
Napi::Env env = info.Env();
|
|
574
|
+
|
|
575
|
+
if (info.Length() < 1 || !info[0].IsNumber())
|
|
576
|
+
{
|
|
577
|
+
Napi::TypeError::New(env, "Expected sample (number)").ThrowAsJavaScriptException();
|
|
578
|
+
return env.Null();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
float sample = info[0].As<Napi::Number>().FloatValue();
|
|
582
|
+
size_t spectrumSize = m_filter->getSpectrumSize();
|
|
583
|
+
std::vector<Complex> spectrum(spectrumSize);
|
|
584
|
+
|
|
585
|
+
bool ready = m_filter->addSample(sample, spectrum.data());
|
|
586
|
+
|
|
587
|
+
if (ready)
|
|
588
|
+
{
|
|
589
|
+
Napi::Float32Array realOut = Napi::Float32Array::New(env, spectrumSize);
|
|
590
|
+
Napi::Float32Array imagOut = Napi::Float32Array::New(env, spectrumSize);
|
|
591
|
+
|
|
592
|
+
for (size_t i = 0; i < spectrumSize; ++i)
|
|
593
|
+
{
|
|
594
|
+
realOut[i] = spectrum[i].real();
|
|
595
|
+
imagOut[i] = spectrum[i].imag();
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
Napi::Object result = Napi::Object::New(env);
|
|
599
|
+
result.Set("real", realOut);
|
|
600
|
+
result.Set("imag", imagOut);
|
|
601
|
+
return result;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return env.Null();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
Napi::Value MovingFftProcessor::AddSamples(const Napi::CallbackInfo &info)
|
|
608
|
+
{
|
|
609
|
+
Napi::Env env = info.Env();
|
|
610
|
+
|
|
611
|
+
if (info.Length() < 2 || !info[0].IsTypedArray() || !info[1].IsFunction())
|
|
612
|
+
{
|
|
613
|
+
Napi::TypeError::New(env, "Expected (Float32Array, callback)").ThrowAsJavaScriptException();
|
|
614
|
+
return env.Null();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
Napi::Float32Array samples = info[0].As<Napi::Float32Array>();
|
|
618
|
+
Napi::Function callback = info[1].As<Napi::Function>();
|
|
619
|
+
|
|
620
|
+
size_t spectrumSize = m_filter->getSpectrumSize();
|
|
621
|
+
|
|
622
|
+
size_t numSpectra = m_filter->addSamples(
|
|
623
|
+
samples.Data(),
|
|
624
|
+
samples.ElementLength(),
|
|
625
|
+
[&](const Complex *spectrum, size_t size)
|
|
626
|
+
{
|
|
627
|
+
Napi::Float32Array realOut = Napi::Float32Array::New(env, size);
|
|
628
|
+
Napi::Float32Array imagOut = Napi::Float32Array::New(env, size);
|
|
629
|
+
|
|
630
|
+
for (size_t i = 0; i < size; ++i)
|
|
631
|
+
{
|
|
632
|
+
realOut[i] = spectrum[i].real();
|
|
633
|
+
imagOut[i] = spectrum[i].imag();
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
Napi::Object result = Napi::Object::New(env);
|
|
637
|
+
result.Set("real", realOut);
|
|
638
|
+
result.Set("imag", imagOut);
|
|
639
|
+
|
|
640
|
+
callback.Call({result, Napi::Number::New(env, size)});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
return Napi::Number::New(env, numSpectra);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
Napi::Value MovingFftProcessor::ComputeSpectrum(const Napi::CallbackInfo &info)
|
|
647
|
+
{
|
|
648
|
+
Napi::Env env = info.Env();
|
|
649
|
+
|
|
650
|
+
size_t spectrumSize = m_filter->getSpectrumSize();
|
|
651
|
+
std::vector<Complex> spectrum(spectrumSize);
|
|
652
|
+
|
|
653
|
+
try
|
|
654
|
+
{
|
|
655
|
+
m_filter->computeSpectrum(spectrum.data());
|
|
656
|
+
}
|
|
657
|
+
catch (const std::exception &e)
|
|
658
|
+
{
|
|
659
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
660
|
+
return env.Null();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
Napi::Float32Array realOut = Napi::Float32Array::New(env, spectrumSize);
|
|
664
|
+
Napi::Float32Array imagOut = Napi::Float32Array::New(env, spectrumSize);
|
|
665
|
+
|
|
666
|
+
for (size_t i = 0; i < spectrumSize; ++i)
|
|
667
|
+
{
|
|
668
|
+
realOut[i] = spectrum[i].real();
|
|
669
|
+
imagOut[i] = spectrum[i].imag();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
Napi::Object result = Napi::Object::New(env);
|
|
673
|
+
result.Set("real", realOut);
|
|
674
|
+
result.Set("imag", imagOut);
|
|
675
|
+
|
|
676
|
+
return result;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
Napi::Value MovingFftProcessor::Reset(const Napi::CallbackInfo &info)
|
|
680
|
+
{
|
|
681
|
+
m_filter->reset();
|
|
682
|
+
return info.Env().Undefined();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
Napi::Value MovingFftProcessor::GetFftSize(const Napi::CallbackInfo &info)
|
|
686
|
+
{
|
|
687
|
+
return Napi::Number::New(info.Env(), m_filter->getFftSize());
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
Napi::Value MovingFftProcessor::GetSpectrumSize(const Napi::CallbackInfo &info)
|
|
691
|
+
{
|
|
692
|
+
return Napi::Number::New(info.Env(), m_filter->getSpectrumSize());
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
Napi::Value MovingFftProcessor::GetHopSize(const Napi::CallbackInfo &info)
|
|
696
|
+
{
|
|
697
|
+
return Napi::Number::New(info.Env(), m_filter->getHopSize());
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
Napi::Value MovingFftProcessor::GetFillLevel(const Napi::CallbackInfo &info)
|
|
701
|
+
{
|
|
702
|
+
return Napi::Number::New(info.Env(), m_filter->getFillLevel());
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
Napi::Value MovingFftProcessor::IsReady(const Napi::CallbackInfo &info)
|
|
706
|
+
{
|
|
707
|
+
return Napi::Boolean::New(info.Env(), m_filter->isReady());
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
Napi::Value MovingFftProcessor::SetWindowType(const Napi::CallbackInfo &info)
|
|
711
|
+
{
|
|
712
|
+
Napi::Env env = info.Env();
|
|
713
|
+
|
|
714
|
+
if (info.Length() < 1 || !info[0].IsString())
|
|
715
|
+
{
|
|
716
|
+
Napi::TypeError::New(env, "Expected window type (string)").ThrowAsJavaScriptException();
|
|
717
|
+
return env.Undefined();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
std::string windowStr = info[0].As<Napi::String>().Utf8Value();
|
|
721
|
+
core::WindowType windowType = core::WindowType::Hann;
|
|
722
|
+
|
|
723
|
+
if (windowStr == "none")
|
|
724
|
+
windowType = core::WindowType::None;
|
|
725
|
+
else if (windowStr == "hann")
|
|
726
|
+
windowType = core::WindowType::Hann;
|
|
727
|
+
else if (windowStr == "hamming")
|
|
728
|
+
windowType = core::WindowType::Hamming;
|
|
729
|
+
else if (windowStr == "blackman")
|
|
730
|
+
windowType = core::WindowType::Blackman;
|
|
731
|
+
else if (windowStr == "bartlett")
|
|
732
|
+
windowType = core::WindowType::Bartlett;
|
|
733
|
+
else
|
|
734
|
+
{
|
|
735
|
+
Napi::TypeError::New(env, "Invalid window type").ThrowAsJavaScriptException();
|
|
736
|
+
return env.Undefined();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
m_filter->setWindowType(windowType);
|
|
740
|
+
return env.Undefined();
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
Napi::Value MovingFftProcessor::GetMagnitudeSpectrum(const Napi::CallbackInfo &info)
|
|
744
|
+
{
|
|
745
|
+
Napi::Env env = info.Env();
|
|
746
|
+
|
|
747
|
+
size_t spectrumSize = m_filter->getSpectrumSize();
|
|
748
|
+
std::vector<float> magnitudes(spectrumSize);
|
|
749
|
+
|
|
750
|
+
m_filter->getMagnitudeSpectrum(magnitudes.data());
|
|
751
|
+
|
|
752
|
+
Napi::Float32Array result = Napi::Float32Array::New(env, spectrumSize);
|
|
753
|
+
std::copy(magnitudes.begin(), magnitudes.end(), result.Data());
|
|
754
|
+
|
|
755
|
+
return result;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
Napi::Value MovingFftProcessor::GetPowerSpectrum(const Napi::CallbackInfo &info)
|
|
759
|
+
{
|
|
760
|
+
Napi::Env env = info.Env();
|
|
761
|
+
|
|
762
|
+
size_t spectrumSize = m_filter->getSpectrumSize();
|
|
763
|
+
std::vector<float> power(spectrumSize);
|
|
764
|
+
|
|
765
|
+
m_filter->getPowerSpectrum(power.data());
|
|
766
|
+
|
|
767
|
+
Napi::Float32Array result = Napi::Float32Array::New(env, spectrumSize);
|
|
768
|
+
std::copy(power.begin(), power.end(), result.Data());
|
|
769
|
+
|
|
770
|
+
return result;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
Napi::Value MovingFftProcessor::GetPhaseSpectrum(const Napi::CallbackInfo &info)
|
|
774
|
+
{
|
|
775
|
+
Napi::Env env = info.Env();
|
|
776
|
+
|
|
777
|
+
size_t spectrumSize = m_filter->getSpectrumSize();
|
|
778
|
+
std::vector<float> phases(spectrumSize);
|
|
779
|
+
|
|
780
|
+
m_filter->getPhaseSpectrum(phases.data());
|
|
781
|
+
|
|
782
|
+
Napi::Float32Array result = Napi::Float32Array::New(env, spectrumSize);
|
|
783
|
+
std::copy(phases.begin(), phases.end(), result.Data());
|
|
784
|
+
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
Napi::Value MovingFftProcessor::GetFrequencyBins(const Napi::CallbackInfo &info)
|
|
789
|
+
{
|
|
790
|
+
Napi::Env env = info.Env();
|
|
791
|
+
|
|
792
|
+
if (info.Length() < 1 || !info[0].IsNumber())
|
|
793
|
+
{
|
|
794
|
+
Napi::TypeError::New(env, "Expected sample rate (number)").ThrowAsJavaScriptException();
|
|
795
|
+
return env.Null();
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
float sampleRate = info[0].As<Napi::Number>().FloatValue();
|
|
799
|
+
size_t spectrumSize = m_filter->getSpectrumSize();
|
|
800
|
+
std::vector<float> frequencies(spectrumSize);
|
|
801
|
+
|
|
802
|
+
m_filter->getFrequencyBins(sampleRate, frequencies.data());
|
|
803
|
+
|
|
804
|
+
Napi::Float32Array result = Napi::Float32Array::New(env, spectrumSize);
|
|
805
|
+
std::copy(frequencies.begin(), frequencies.end(), result.Data());
|
|
806
|
+
|
|
807
|
+
return result;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Export init function (add to existing module init)
|
|
811
|
+
void InitFftBindings(Napi::Env env, Napi::Object exports)
|
|
812
|
+
{
|
|
813
|
+
FftProcessor::Init(env, exports);
|
|
814
|
+
MovingFftProcessor::Init(env, exports);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
} // namespace dsp
|