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,1001 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* N-API Bindings for FIR and IIR Filters
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include <napi.h>
|
|
6
|
+
#include "core/FirFilter.h"
|
|
7
|
+
#include "core/IirFilter.h"
|
|
8
|
+
#include "utils/NapiUtils.h"
|
|
9
|
+
#include <memory>
|
|
10
|
+
|
|
11
|
+
namespace dsp
|
|
12
|
+
{
|
|
13
|
+
// ========== FIR Filter Bindings ==========
|
|
14
|
+
|
|
15
|
+
class FirFilterWrapper : public Napi::ObjectWrap<FirFilterWrapper>
|
|
16
|
+
{
|
|
17
|
+
public:
|
|
18
|
+
static inline Napi::FunctionReference constructor;
|
|
19
|
+
|
|
20
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports)
|
|
21
|
+
{
|
|
22
|
+
Napi::Function func = DefineClass(env, "FirFilter", {
|
|
23
|
+
InstanceMethod("processSample", &FirFilterWrapper::ProcessSample),
|
|
24
|
+
InstanceMethod("process", &FirFilterWrapper::Process),
|
|
25
|
+
InstanceMethod("reset", &FirFilterWrapper::Reset),
|
|
26
|
+
InstanceMethod("getOrder", &FirFilterWrapper::GetOrder),
|
|
27
|
+
InstanceMethod("getCoefficients", &FirFilterWrapper::GetCoefficients),
|
|
28
|
+
InstanceMethod("setCoefficients", &FirFilterWrapper::SetCoefficients),
|
|
29
|
+
InstanceMethod("isStateful", &FirFilterWrapper::IsStateful),
|
|
30
|
+
StaticMethod("createLowPass", &FirFilterWrapper::CreateLowPass),
|
|
31
|
+
StaticMethod("createHighPass", &FirFilterWrapper::CreateHighPass),
|
|
32
|
+
StaticMethod("createBandPass", &FirFilterWrapper::CreateBandPass),
|
|
33
|
+
StaticMethod("createBandStop", &FirFilterWrapper::CreateBandStop),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
constructor = Napi::Persistent(func);
|
|
37
|
+
constructor.SuppressDestruct();
|
|
38
|
+
|
|
39
|
+
exports.Set("FirFilter", func);
|
|
40
|
+
return exports;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
FirFilterWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<FirFilterWrapper>(info)
|
|
44
|
+
{
|
|
45
|
+
Napi::Env env = info.Env();
|
|
46
|
+
|
|
47
|
+
if (info.Length() < 1 || !info[0].IsArray())
|
|
48
|
+
{
|
|
49
|
+
Napi::TypeError::New(env, "Expected coefficients array").ThrowAsJavaScriptException();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Napi::Array coeffsArray = info[0].As<Napi::Array>();
|
|
54
|
+
std::vector<float> coeffs;
|
|
55
|
+
|
|
56
|
+
for (uint32_t i = 0; i < coeffsArray.Length(); ++i)
|
|
57
|
+
{
|
|
58
|
+
Napi::Value val = coeffsArray[i];
|
|
59
|
+
if (val.IsNumber())
|
|
60
|
+
{
|
|
61
|
+
coeffs.push_back(val.As<Napi::Number>().FloatValue());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
bool stateful = true;
|
|
66
|
+
if (info.Length() >= 2 && info[1].IsBoolean())
|
|
67
|
+
{
|
|
68
|
+
stateful = info[1].As<Napi::Boolean>().Value();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
m_filter = std::make_unique<core::FirFilter<float>>(coeffs, stateful);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private:
|
|
75
|
+
std::unique_ptr<core::FirFilter<float>> m_filter;
|
|
76
|
+
|
|
77
|
+
Napi::Value ProcessSample(const Napi::CallbackInfo &info)
|
|
78
|
+
{
|
|
79
|
+
Napi::Env env = info.Env();
|
|
80
|
+
|
|
81
|
+
if (info.Length() < 1 || !info[0].IsNumber())
|
|
82
|
+
{
|
|
83
|
+
Napi::TypeError::New(env, "Expected number").ThrowAsJavaScriptException();
|
|
84
|
+
return env.Null();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
float input = info[0].As<Napi::Number>().FloatValue();
|
|
88
|
+
float output = m_filter->processSample(input);
|
|
89
|
+
|
|
90
|
+
return Napi::Number::New(env, output);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Napi::Value Process(const Napi::CallbackInfo &info)
|
|
94
|
+
{
|
|
95
|
+
Napi::Env env = info.Env();
|
|
96
|
+
|
|
97
|
+
if (info.Length() < 1 || !info[0].IsTypedArray())
|
|
98
|
+
{
|
|
99
|
+
Napi::TypeError::New(env, "Expected Float32Array").ThrowAsJavaScriptException();
|
|
100
|
+
return env.Null();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Napi::Float32Array inputArray = info[0].As<Napi::Float32Array>();
|
|
104
|
+
size_t length = inputArray.ElementLength();
|
|
105
|
+
|
|
106
|
+
bool stateless = false;
|
|
107
|
+
if (info.Length() >= 2 && info[1].IsBoolean())
|
|
108
|
+
{
|
|
109
|
+
stateless = info[1].As<Napi::Boolean>().Value();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Napi::Float32Array outputArray = Napi::Float32Array::New(env, length);
|
|
113
|
+
|
|
114
|
+
m_filter->process(inputArray.Data(), outputArray.Data(), length, stateless);
|
|
115
|
+
|
|
116
|
+
return outputArray;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Napi::Value Reset(const Napi::CallbackInfo &info)
|
|
120
|
+
{
|
|
121
|
+
m_filter->reset();
|
|
122
|
+
return info.Env().Undefined();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
Napi::Value GetOrder(const Napi::CallbackInfo &info)
|
|
126
|
+
{
|
|
127
|
+
return Napi::Number::New(info.Env(), m_filter->getOrder());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
Napi::Value GetCoefficients(const Napi::CallbackInfo &info)
|
|
131
|
+
{
|
|
132
|
+
Napi::Env env = info.Env();
|
|
133
|
+
const auto &coeffs = m_filter->getCoefficients();
|
|
134
|
+
|
|
135
|
+
Napi::Array result = Napi::Array::New(env, coeffs.size());
|
|
136
|
+
for (size_t i = 0; i < coeffs.size(); ++i)
|
|
137
|
+
{
|
|
138
|
+
result[i] = Napi::Number::New(env, coeffs[i]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Napi::Value SetCoefficients(const Napi::CallbackInfo &info)
|
|
145
|
+
{
|
|
146
|
+
Napi::Env env = info.Env();
|
|
147
|
+
|
|
148
|
+
if (info.Length() < 1 || !info[0].IsArray())
|
|
149
|
+
{
|
|
150
|
+
Napi::TypeError::New(env, "Expected coefficients array").ThrowAsJavaScriptException();
|
|
151
|
+
return env.Undefined();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Napi::Array coeffsArray = info[0].As<Napi::Array>();
|
|
155
|
+
std::vector<float> coeffs;
|
|
156
|
+
|
|
157
|
+
for (uint32_t i = 0; i < coeffsArray.Length(); ++i)
|
|
158
|
+
{
|
|
159
|
+
Napi::Value val = coeffsArray[i];
|
|
160
|
+
if (val.IsNumber())
|
|
161
|
+
{
|
|
162
|
+
coeffs.push_back(val.As<Napi::Number>().FloatValue());
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
m_filter->setCoefficients(coeffs);
|
|
167
|
+
return env.Undefined();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
Napi::Value IsStateful(const Napi::CallbackInfo &info)
|
|
171
|
+
{
|
|
172
|
+
return Napi::Boolean::New(info.Env(), m_filter->isStateful());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static Napi::Value CreateLowPass(const Napi::CallbackInfo &info)
|
|
176
|
+
{
|
|
177
|
+
Napi::Env env = info.Env();
|
|
178
|
+
|
|
179
|
+
if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber())
|
|
180
|
+
{
|
|
181
|
+
Napi::TypeError::New(env, "Expected cutoffFreq and numTaps").ThrowAsJavaScriptException();
|
|
182
|
+
return env.Null();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
186
|
+
size_t numTaps = info[1].As<Napi::Number>().Uint32Value();
|
|
187
|
+
|
|
188
|
+
std::string windowType = "hamming";
|
|
189
|
+
if (info.Length() >= 3 && info[2].IsString())
|
|
190
|
+
{
|
|
191
|
+
windowType = info[2].As<Napi::String>().Utf8Value();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
auto filter = core::FirFilter<float>::createLowPass(cutoffFreq, numTaps, windowType);
|
|
195
|
+
auto coeffs = filter.getCoefficients();
|
|
196
|
+
|
|
197
|
+
Napi::Array coeffsArray = Napi::Array::New(env, coeffs.size());
|
|
198
|
+
for (size_t i = 0; i < coeffs.size(); ++i)
|
|
199
|
+
{
|
|
200
|
+
coeffsArray[i] = Napi::Number::New(env, coeffs[i]);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return constructor.New({coeffsArray});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static Napi::Value CreateHighPass(const Napi::CallbackInfo &info)
|
|
207
|
+
{
|
|
208
|
+
Napi::Env env = info.Env();
|
|
209
|
+
|
|
210
|
+
if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber())
|
|
211
|
+
{
|
|
212
|
+
Napi::TypeError::New(env, "Expected cutoffFreq and numTaps").ThrowAsJavaScriptException();
|
|
213
|
+
return env.Null();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
217
|
+
size_t numTaps = info[1].As<Napi::Number>().Uint32Value();
|
|
218
|
+
|
|
219
|
+
std::string windowType = "hamming";
|
|
220
|
+
if (info.Length() >= 3 && info[2].IsString())
|
|
221
|
+
{
|
|
222
|
+
windowType = info[2].As<Napi::String>().Utf8Value();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
auto filter = core::FirFilter<float>::createHighPass(cutoffFreq, numTaps, windowType);
|
|
226
|
+
auto coeffs = filter.getCoefficients();
|
|
227
|
+
|
|
228
|
+
Napi::Array coeffsArray = Napi::Array::New(env, coeffs.size());
|
|
229
|
+
for (size_t i = 0; i < coeffs.size(); ++i)
|
|
230
|
+
{
|
|
231
|
+
coeffsArray[i] = Napi::Number::New(env, coeffs[i]);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return constructor.New({coeffsArray});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
static Napi::Value CreateBandPass(const Napi::CallbackInfo &info)
|
|
238
|
+
{
|
|
239
|
+
Napi::Env env = info.Env();
|
|
240
|
+
|
|
241
|
+
if (info.Length() < 3)
|
|
242
|
+
{
|
|
243
|
+
Napi::TypeError::New(env, "Expected lowCutoff, highCutoff, numTaps").ThrowAsJavaScriptException();
|
|
244
|
+
return env.Null();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
float lowCutoff = info[0].As<Napi::Number>().FloatValue();
|
|
248
|
+
float highCutoff = info[1].As<Napi::Number>().FloatValue();
|
|
249
|
+
size_t numTaps = info[2].As<Napi::Number>().Uint32Value();
|
|
250
|
+
|
|
251
|
+
std::string windowType = "hamming";
|
|
252
|
+
if (info.Length() >= 4 && info[3].IsString())
|
|
253
|
+
{
|
|
254
|
+
windowType = info[3].As<Napi::String>().Utf8Value();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
auto filter = core::FirFilter<float>::createBandPass(lowCutoff, highCutoff, numTaps, windowType);
|
|
258
|
+
auto coeffs = filter.getCoefficients();
|
|
259
|
+
|
|
260
|
+
Napi::Array coeffsArray = Napi::Array::New(env, coeffs.size());
|
|
261
|
+
for (size_t i = 0; i < coeffs.size(); ++i)
|
|
262
|
+
{
|
|
263
|
+
coeffsArray[i] = Napi::Number::New(env, coeffs[i]);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return constructor.New({coeffsArray});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
static Napi::Value CreateBandStop(const Napi::CallbackInfo &info)
|
|
270
|
+
{
|
|
271
|
+
Napi::Env env = info.Env();
|
|
272
|
+
|
|
273
|
+
if (info.Length() < 3)
|
|
274
|
+
{
|
|
275
|
+
Napi::TypeError::New(env, "Expected lowCutoff, highCutoff, numTaps").ThrowAsJavaScriptException();
|
|
276
|
+
return env.Null();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
float lowCutoff = info[0].As<Napi::Number>().FloatValue();
|
|
280
|
+
float highCutoff = info[1].As<Napi::Number>().FloatValue();
|
|
281
|
+
size_t numTaps = info[2].As<Napi::Number>().Uint32Value();
|
|
282
|
+
|
|
283
|
+
std::string windowType = "hamming";
|
|
284
|
+
if (info.Length() >= 4 && info[3].IsString())
|
|
285
|
+
{
|
|
286
|
+
windowType = info[3].As<Napi::String>().Utf8Value();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
auto filter = core::FirFilter<float>::createBandStop(lowCutoff, highCutoff, numTaps, windowType);
|
|
290
|
+
auto coeffs = filter.getCoefficients();
|
|
291
|
+
|
|
292
|
+
Napi::Array coeffsArray = Napi::Array::New(env, coeffs.size());
|
|
293
|
+
for (size_t i = 0; i < coeffs.size(); ++i)
|
|
294
|
+
{
|
|
295
|
+
coeffsArray[i] = Napi::Number::New(env, coeffs[i]);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return constructor.New({coeffsArray});
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// ========== IIR Filter Bindings ==========
|
|
303
|
+
|
|
304
|
+
class IirFilterWrapper : public Napi::ObjectWrap<IirFilterWrapper>
|
|
305
|
+
{
|
|
306
|
+
public:
|
|
307
|
+
static inline Napi::FunctionReference constructor;
|
|
308
|
+
|
|
309
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports)
|
|
310
|
+
{
|
|
311
|
+
Napi::Function func = DefineClass(env, "IirFilter", {
|
|
312
|
+
InstanceMethod("processSample", &IirFilterWrapper::ProcessSample),
|
|
313
|
+
InstanceMethod("process", &IirFilterWrapper::Process),
|
|
314
|
+
InstanceMethod("reset", &IirFilterWrapper::Reset),
|
|
315
|
+
InstanceMethod("getFeedforwardOrder", &IirFilterWrapper::GetFeedforwardOrder),
|
|
316
|
+
InstanceMethod("getFeedbackOrder", &IirFilterWrapper::GetFeedbackOrder),
|
|
317
|
+
InstanceMethod("getBCoefficients", &IirFilterWrapper::GetBCoefficients),
|
|
318
|
+
InstanceMethod("getACoefficients", &IirFilterWrapper::GetACoefficients),
|
|
319
|
+
InstanceMethod("setCoefficients", &IirFilterWrapper::SetCoefficients),
|
|
320
|
+
InstanceMethod("isStateful", &IirFilterWrapper::IsStateful),
|
|
321
|
+
InstanceMethod("isStable", &IirFilterWrapper::IsStable),
|
|
322
|
+
StaticMethod("createFirstOrderLowPass", &IirFilterWrapper::CreateFirstOrderLowPass),
|
|
323
|
+
StaticMethod("createFirstOrderHighPass", &IirFilterWrapper::CreateFirstOrderHighPass),
|
|
324
|
+
StaticMethod("createButterworthLowPass", &IirFilterWrapper::CreateButterworthLowPass),
|
|
325
|
+
StaticMethod("createButterworthHighPass", &IirFilterWrapper::CreateButterworthHighPass),
|
|
326
|
+
StaticMethod("createButterworthBandPass", &IirFilterWrapper::CreateButterworthBandPass),
|
|
327
|
+
StaticMethod("createBiquad", &IirFilterWrapper::CreateBiquad),
|
|
328
|
+
StaticMethod("createChebyshevLowPass", &IirFilterWrapper::CreateChebyshevLowPass),
|
|
329
|
+
StaticMethod("createChebyshevHighPass", &IirFilterWrapper::CreateChebyshevHighPass),
|
|
330
|
+
StaticMethod("createChebyshevBandPass", &IirFilterWrapper::CreateChebyshevBandPass),
|
|
331
|
+
StaticMethod("createPeakingEQ", &IirFilterWrapper::CreatePeakingEQ),
|
|
332
|
+
StaticMethod("createLowShelf", &IirFilterWrapper::CreateLowShelf),
|
|
333
|
+
StaticMethod("createHighShelf", &IirFilterWrapper::CreateHighShelf),
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
constructor = Napi::Persistent(func);
|
|
337
|
+
constructor.SuppressDestruct();
|
|
338
|
+
|
|
339
|
+
exports.Set("IirFilter", func);
|
|
340
|
+
return exports;
|
|
341
|
+
}
|
|
342
|
+
IirFilterWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<IirFilterWrapper>(info)
|
|
343
|
+
{
|
|
344
|
+
Napi::Env env = info.Env();
|
|
345
|
+
|
|
346
|
+
if (info.Length() < 2 || !info[0].IsArray() || !info[1].IsArray())
|
|
347
|
+
{
|
|
348
|
+
Napi::TypeError::New(env, "Expected b_coeffs and a_coeffs arrays").ThrowAsJavaScriptException();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
Napi::Array bArray = info[0].As<Napi::Array>();
|
|
353
|
+
Napi::Array aArray = info[1].As<Napi::Array>();
|
|
354
|
+
|
|
355
|
+
std::vector<float> b_coeffs, a_coeffs;
|
|
356
|
+
|
|
357
|
+
for (uint32_t i = 0; i < bArray.Length(); ++i)
|
|
358
|
+
{
|
|
359
|
+
b_coeffs.push_back(bArray.Get(i).As<Napi::Number>().FloatValue());
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (uint32_t i = 0; i < aArray.Length(); ++i)
|
|
363
|
+
{
|
|
364
|
+
a_coeffs.push_back(aArray.Get(i).As<Napi::Number>().FloatValue());
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
bool stateful = true;
|
|
368
|
+
if (info.Length() >= 3 && info[2].IsBoolean())
|
|
369
|
+
{
|
|
370
|
+
stateful = info[2].As<Napi::Boolean>().Value();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
m_filter = std::make_unique<core::IirFilter<float>>(b_coeffs, a_coeffs, stateful);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private:
|
|
377
|
+
std::unique_ptr<core::IirFilter<float>> m_filter;
|
|
378
|
+
|
|
379
|
+
Napi::Value ProcessSample(const Napi::CallbackInfo &info)
|
|
380
|
+
{
|
|
381
|
+
Napi::Env env = info.Env();
|
|
382
|
+
|
|
383
|
+
if (info.Length() < 1 || !info[0].IsNumber())
|
|
384
|
+
{
|
|
385
|
+
Napi::TypeError::New(env, "Expected number").ThrowAsJavaScriptException();
|
|
386
|
+
return env.Null();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
float input = info[0].As<Napi::Number>().FloatValue();
|
|
390
|
+
float output = m_filter->processSample(input);
|
|
391
|
+
|
|
392
|
+
return Napi::Number::New(env, output);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
Napi::Value Process(const Napi::CallbackInfo &info)
|
|
396
|
+
{
|
|
397
|
+
Napi::Env env = info.Env();
|
|
398
|
+
|
|
399
|
+
if (info.Length() < 1 || !info[0].IsTypedArray())
|
|
400
|
+
{
|
|
401
|
+
Napi::TypeError::New(env, "Expected Float32Array").ThrowAsJavaScriptException();
|
|
402
|
+
return env.Null();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
Napi::Float32Array inputArray = info[0].As<Napi::Float32Array>();
|
|
406
|
+
size_t length = inputArray.ElementLength();
|
|
407
|
+
|
|
408
|
+
bool stateless = false;
|
|
409
|
+
if (info.Length() >= 2 && info[1].IsBoolean())
|
|
410
|
+
{
|
|
411
|
+
stateless = info[1].As<Napi::Boolean>().Value();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
Napi::Float32Array outputArray = Napi::Float32Array::New(env, length);
|
|
415
|
+
|
|
416
|
+
m_filter->process(inputArray.Data(), outputArray.Data(), length, stateless);
|
|
417
|
+
|
|
418
|
+
return outputArray;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
Napi::Value Reset(const Napi::CallbackInfo &info)
|
|
422
|
+
{
|
|
423
|
+
m_filter->reset();
|
|
424
|
+
return info.Env().Undefined();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
Napi::Value GetFeedforwardOrder(const Napi::CallbackInfo &info)
|
|
428
|
+
{
|
|
429
|
+
return Napi::Number::New(info.Env(), m_filter->getFeedforwardOrder());
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
Napi::Value GetFeedbackOrder(const Napi::CallbackInfo &info)
|
|
433
|
+
{
|
|
434
|
+
return Napi::Number::New(info.Env(), m_filter->getFeedbackOrder());
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
Napi::Value GetBCoefficients(const Napi::CallbackInfo &info)
|
|
438
|
+
{
|
|
439
|
+
Napi::Env env = info.Env();
|
|
440
|
+
const auto &coeffs = m_filter->getBCoefficients();
|
|
441
|
+
|
|
442
|
+
Napi::Array result = Napi::Array::New(env, coeffs.size());
|
|
443
|
+
for (size_t i = 0; i < coeffs.size(); ++i)
|
|
444
|
+
{
|
|
445
|
+
result[i] = Napi::Number::New(env, coeffs[i]);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
Napi::Value GetACoefficients(const Napi::CallbackInfo &info)
|
|
452
|
+
{
|
|
453
|
+
Napi::Env env = info.Env();
|
|
454
|
+
const auto &coeffs = m_filter->getACoefficients();
|
|
455
|
+
|
|
456
|
+
Napi::Array result = Napi::Array::New(env, coeffs.size());
|
|
457
|
+
for (size_t i = 0; i < coeffs.size(); ++i)
|
|
458
|
+
{
|
|
459
|
+
result[i] = Napi::Number::New(env, coeffs[i]);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return result;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
Napi::Value SetCoefficients(const Napi::CallbackInfo &info)
|
|
466
|
+
{
|
|
467
|
+
Napi::Env env = info.Env();
|
|
468
|
+
|
|
469
|
+
if (info.Length() < 2 || !info[0].IsArray() || !info[1].IsArray())
|
|
470
|
+
{
|
|
471
|
+
Napi::TypeError::New(env, "Expected b_coeffs and a_coeffs arrays").ThrowAsJavaScriptException();
|
|
472
|
+
return env.Undefined();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
Napi::Array bArray = info[0].As<Napi::Array>();
|
|
476
|
+
Napi::Array aArray = info[1].As<Napi::Array>();
|
|
477
|
+
|
|
478
|
+
std::vector<float> b_coeffs, a_coeffs;
|
|
479
|
+
|
|
480
|
+
for (uint32_t i = 0; i < bArray.Length(); ++i)
|
|
481
|
+
{
|
|
482
|
+
b_coeffs.push_back(bArray.Get(i).As<Napi::Number>().FloatValue());
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
for (uint32_t i = 0; i < aArray.Length(); ++i)
|
|
486
|
+
{
|
|
487
|
+
a_coeffs.push_back(aArray.Get(i).As<Napi::Number>().FloatValue());
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
m_filter->setCoefficients(b_coeffs, a_coeffs);
|
|
491
|
+
return env.Undefined();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
Napi::Value IsStateful(const Napi::CallbackInfo &info)
|
|
495
|
+
{
|
|
496
|
+
return Napi::Boolean::New(info.Env(), m_filter->isStateful());
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
Napi::Value IsStable(const Napi::CallbackInfo &info)
|
|
500
|
+
{
|
|
501
|
+
return Napi::Boolean::New(info.Env(), m_filter->isStable());
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Static factory methods
|
|
505
|
+
static Napi::Value CreateFirstOrderLowPass(const Napi::CallbackInfo &info)
|
|
506
|
+
{
|
|
507
|
+
Napi::Env env = info.Env();
|
|
508
|
+
|
|
509
|
+
if (info.Length() < 1 || !info[0].IsNumber())
|
|
510
|
+
{
|
|
511
|
+
Napi::TypeError::New(env, "Expected cutoffFreq").ThrowAsJavaScriptException();
|
|
512
|
+
return env.Null();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
516
|
+
auto filter = core::IirFilter<float>::createFirstOrderLowPass(cutoffFreq);
|
|
517
|
+
|
|
518
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
519
|
+
auto a_coeffs = filter.getACoefficients();
|
|
520
|
+
|
|
521
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
522
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
523
|
+
|
|
524
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
525
|
+
{
|
|
526
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
530
|
+
{
|
|
531
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return constructor.New({bArray, aArray});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
static Napi::Value CreateFirstOrderHighPass(const Napi::CallbackInfo &info)
|
|
538
|
+
{
|
|
539
|
+
Napi::Env env = info.Env();
|
|
540
|
+
|
|
541
|
+
if (info.Length() < 1 || !info[0].IsNumber())
|
|
542
|
+
{
|
|
543
|
+
Napi::TypeError::New(env, "Expected cutoffFreq").ThrowAsJavaScriptException();
|
|
544
|
+
return env.Null();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
548
|
+
auto filter = core::IirFilter<float>::createFirstOrderHighPass(cutoffFreq);
|
|
549
|
+
|
|
550
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
551
|
+
auto a_coeffs = filter.getACoefficients();
|
|
552
|
+
|
|
553
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
554
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
555
|
+
|
|
556
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
557
|
+
{
|
|
558
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
562
|
+
{
|
|
563
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return constructor.New({bArray, aArray});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
static Napi::Value CreateButterworthLowPass(const Napi::CallbackInfo &info)
|
|
570
|
+
{
|
|
571
|
+
Napi::Env env = info.Env();
|
|
572
|
+
|
|
573
|
+
if (info.Length() < 2)
|
|
574
|
+
{
|
|
575
|
+
Napi::TypeError::New(env, "Expected cutoffFreq and order").ThrowAsJavaScriptException();
|
|
576
|
+
return env.Null();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
580
|
+
int order = info[1].As<Napi::Number>().Int32Value();
|
|
581
|
+
|
|
582
|
+
auto filter = core::IirFilter<float>::createButterworthLowPass(cutoffFreq, order);
|
|
583
|
+
|
|
584
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
585
|
+
auto a_coeffs = filter.getACoefficients();
|
|
586
|
+
|
|
587
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
588
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
589
|
+
|
|
590
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
591
|
+
{
|
|
592
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
596
|
+
{
|
|
597
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return constructor.New({bArray, aArray});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
static Napi::Value CreateButterworthHighPass(const Napi::CallbackInfo &info)
|
|
604
|
+
{
|
|
605
|
+
Napi::Env env = info.Env();
|
|
606
|
+
|
|
607
|
+
if (info.Length() < 2)
|
|
608
|
+
{
|
|
609
|
+
Napi::TypeError::New(env, "Expected cutoffFreq and order").ThrowAsJavaScriptException();
|
|
610
|
+
return env.Null();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
614
|
+
int order = info[1].As<Napi::Number>().Int32Value();
|
|
615
|
+
|
|
616
|
+
auto filter = core::IirFilter<float>::createButterworthHighPass(cutoffFreq, order);
|
|
617
|
+
|
|
618
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
619
|
+
auto a_coeffs = filter.getACoefficients();
|
|
620
|
+
|
|
621
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
622
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
623
|
+
|
|
624
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
625
|
+
{
|
|
626
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
630
|
+
{
|
|
631
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return constructor.New({bArray, aArray});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
static Napi::Value CreateButterworthBandPass(const Napi::CallbackInfo &info)
|
|
638
|
+
{
|
|
639
|
+
Napi::Env env = info.Env();
|
|
640
|
+
|
|
641
|
+
if (info.Length() < 3)
|
|
642
|
+
{
|
|
643
|
+
Napi::TypeError::New(env, "Expected lowCutoff, highCutoff, order").ThrowAsJavaScriptException();
|
|
644
|
+
return env.Null();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
float lowCutoff = info[0].As<Napi::Number>().FloatValue();
|
|
648
|
+
float highCutoff = info[1].As<Napi::Number>().FloatValue();
|
|
649
|
+
int order = info[2].As<Napi::Number>().Int32Value();
|
|
650
|
+
|
|
651
|
+
auto filter = core::IirFilter<float>::createButterworthBandPass(lowCutoff, highCutoff, order);
|
|
652
|
+
|
|
653
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
654
|
+
auto a_coeffs = filter.getACoefficients();
|
|
655
|
+
|
|
656
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
657
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
658
|
+
|
|
659
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
660
|
+
{
|
|
661
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
665
|
+
{
|
|
666
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return constructor.New({bArray, aArray});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
static Napi::Value CreateBiquad(const Napi::CallbackInfo &info)
|
|
673
|
+
{
|
|
674
|
+
Napi::Env env = info.Env();
|
|
675
|
+
|
|
676
|
+
if (info.Length() < 5)
|
|
677
|
+
{
|
|
678
|
+
Napi::TypeError::New(env, "Expected b0, b1, b2, a1, a2").ThrowAsJavaScriptException();
|
|
679
|
+
return env.Null();
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
float b0 = info[0].As<Napi::Number>().FloatValue();
|
|
683
|
+
float b1 = info[1].As<Napi::Number>().FloatValue();
|
|
684
|
+
float b2 = info[2].As<Napi::Number>().FloatValue();
|
|
685
|
+
float a1 = info[3].As<Napi::Number>().FloatValue();
|
|
686
|
+
float a2 = info[4].As<Napi::Number>().FloatValue();
|
|
687
|
+
|
|
688
|
+
auto filter = core::IirFilter<float>::createBiquad(b0, b1, b2, a1, a2);
|
|
689
|
+
|
|
690
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
691
|
+
auto a_coeffs = filter.getACoefficients();
|
|
692
|
+
|
|
693
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
694
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
695
|
+
|
|
696
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
697
|
+
{
|
|
698
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
702
|
+
{
|
|
703
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return constructor.New({bArray, aArray});
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
static Napi::Value CreateChebyshevLowPass(const Napi::CallbackInfo &info)
|
|
710
|
+
{
|
|
711
|
+
Napi::Env env = info.Env();
|
|
712
|
+
|
|
713
|
+
if (info.Length() < 2)
|
|
714
|
+
{
|
|
715
|
+
Napi::TypeError::New(env, "Expected cutoffFreq, order, and optional rippleDb").ThrowAsJavaScriptException();
|
|
716
|
+
return env.Null();
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
720
|
+
int order = info[1].As<Napi::Number>().Int32Value();
|
|
721
|
+
float rippleDb = 0.5f; // Default ripple
|
|
722
|
+
|
|
723
|
+
if (info.Length() >= 3 && info[2].IsNumber())
|
|
724
|
+
{
|
|
725
|
+
rippleDb = info[2].As<Napi::Number>().FloatValue();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
try
|
|
729
|
+
{
|
|
730
|
+
auto filter = core::IirFilter<float>::createChebyshevLowPass(cutoffFreq, order, rippleDb);
|
|
731
|
+
|
|
732
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
733
|
+
auto a_coeffs = filter.getACoefficients();
|
|
734
|
+
|
|
735
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
736
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
737
|
+
|
|
738
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
739
|
+
{
|
|
740
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
744
|
+
{
|
|
745
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return constructor.New({bArray, aArray});
|
|
749
|
+
}
|
|
750
|
+
catch (const std::exception &e)
|
|
751
|
+
{
|
|
752
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
753
|
+
return env.Null();
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
static Napi::Value CreateChebyshevHighPass(const Napi::CallbackInfo &info)
|
|
758
|
+
{
|
|
759
|
+
Napi::Env env = info.Env();
|
|
760
|
+
|
|
761
|
+
if (info.Length() < 2)
|
|
762
|
+
{
|
|
763
|
+
Napi::TypeError::New(env, "Expected cutoffFreq, order, and optional rippleDb").ThrowAsJavaScriptException();
|
|
764
|
+
return env.Null();
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
768
|
+
int order = info[1].As<Napi::Number>().Int32Value();
|
|
769
|
+
float rippleDb = 0.5f;
|
|
770
|
+
|
|
771
|
+
if (info.Length() >= 3 && info[2].IsNumber())
|
|
772
|
+
{
|
|
773
|
+
rippleDb = info[2].As<Napi::Number>().FloatValue();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
try
|
|
777
|
+
{
|
|
778
|
+
auto filter = core::IirFilter<float>::createChebyshevHighPass(cutoffFreq, order, rippleDb);
|
|
779
|
+
|
|
780
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
781
|
+
auto a_coeffs = filter.getACoefficients();
|
|
782
|
+
|
|
783
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
784
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
785
|
+
|
|
786
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
787
|
+
{
|
|
788
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
792
|
+
{
|
|
793
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return constructor.New({bArray, aArray});
|
|
797
|
+
}
|
|
798
|
+
catch (const std::exception &e)
|
|
799
|
+
{
|
|
800
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
801
|
+
return env.Null();
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
static Napi::Value CreateChebyshevBandPass(const Napi::CallbackInfo &info)
|
|
806
|
+
{
|
|
807
|
+
Napi::Env env = info.Env();
|
|
808
|
+
|
|
809
|
+
if (info.Length() < 3)
|
|
810
|
+
{
|
|
811
|
+
Napi::TypeError::New(env, "Expected lowCutoff, highCutoff, order, and optional rippleDb").ThrowAsJavaScriptException();
|
|
812
|
+
return env.Null();
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
float lowCutoff = info[0].As<Napi::Number>().FloatValue();
|
|
816
|
+
float highCutoff = info[1].As<Napi::Number>().FloatValue();
|
|
817
|
+
int order = info[2].As<Napi::Number>().Int32Value();
|
|
818
|
+
float rippleDb = 0.5f;
|
|
819
|
+
|
|
820
|
+
if (info.Length() >= 4 && info[3].IsNumber())
|
|
821
|
+
{
|
|
822
|
+
rippleDb = info[3].As<Napi::Number>().FloatValue();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
try
|
|
826
|
+
{
|
|
827
|
+
auto filter = core::IirFilter<float>::createChebyshevBandPass(lowCutoff, highCutoff, order, rippleDb);
|
|
828
|
+
|
|
829
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
830
|
+
auto a_coeffs = filter.getACoefficients();
|
|
831
|
+
|
|
832
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
833
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
834
|
+
|
|
835
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
836
|
+
{
|
|
837
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
841
|
+
{
|
|
842
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return constructor.New({bArray, aArray});
|
|
846
|
+
}
|
|
847
|
+
catch (const std::exception &e)
|
|
848
|
+
{
|
|
849
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
850
|
+
return env.Null();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
static Napi::Value CreatePeakingEQ(const Napi::CallbackInfo &info)
|
|
855
|
+
{
|
|
856
|
+
Napi::Env env = info.Env();
|
|
857
|
+
|
|
858
|
+
if (info.Length() < 3)
|
|
859
|
+
{
|
|
860
|
+
Napi::TypeError::New(env, "Expected centerFreq, Q, and gainDb").ThrowAsJavaScriptException();
|
|
861
|
+
return env.Null();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
float centerFreq = info[0].As<Napi::Number>().FloatValue();
|
|
865
|
+
float Q = info[1].As<Napi::Number>().FloatValue();
|
|
866
|
+
float gainDb = info[2].As<Napi::Number>().FloatValue();
|
|
867
|
+
|
|
868
|
+
try
|
|
869
|
+
{
|
|
870
|
+
auto filter = core::IirFilter<float>::createPeakingEQ(centerFreq, Q, gainDb);
|
|
871
|
+
|
|
872
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
873
|
+
auto a_coeffs = filter.getACoefficients();
|
|
874
|
+
|
|
875
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
876
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
877
|
+
|
|
878
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
879
|
+
{
|
|
880
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
884
|
+
{
|
|
885
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return constructor.New({bArray, aArray});
|
|
889
|
+
}
|
|
890
|
+
catch (const std::exception &e)
|
|
891
|
+
{
|
|
892
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
893
|
+
return env.Null();
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
static Napi::Value CreateLowShelf(const Napi::CallbackInfo &info)
|
|
898
|
+
{
|
|
899
|
+
Napi::Env env = info.Env();
|
|
900
|
+
|
|
901
|
+
if (info.Length() < 2)
|
|
902
|
+
{
|
|
903
|
+
Napi::TypeError::New(env, "Expected cutoffFreq, gainDb, and optional Q").ThrowAsJavaScriptException();
|
|
904
|
+
return env.Null();
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
908
|
+
float gainDb = info[1].As<Napi::Number>().FloatValue();
|
|
909
|
+
float Q = 0.707f; // Default Q
|
|
910
|
+
|
|
911
|
+
if (info.Length() >= 3 && info[2].IsNumber())
|
|
912
|
+
{
|
|
913
|
+
Q = info[2].As<Napi::Number>().FloatValue();
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
try
|
|
917
|
+
{
|
|
918
|
+
auto filter = core::IirFilter<float>::createLowShelf(cutoffFreq, gainDb, Q);
|
|
919
|
+
|
|
920
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
921
|
+
auto a_coeffs = filter.getACoefficients();
|
|
922
|
+
|
|
923
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
924
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
925
|
+
|
|
926
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
927
|
+
{
|
|
928
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
932
|
+
{
|
|
933
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return constructor.New({bArray, aArray});
|
|
937
|
+
}
|
|
938
|
+
catch (const std::exception &e)
|
|
939
|
+
{
|
|
940
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
941
|
+
return env.Null();
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
static Napi::Value CreateHighShelf(const Napi::CallbackInfo &info)
|
|
946
|
+
{
|
|
947
|
+
Napi::Env env = info.Env();
|
|
948
|
+
|
|
949
|
+
if (info.Length() < 2)
|
|
950
|
+
{
|
|
951
|
+
Napi::TypeError::New(env, "Expected cutoffFreq, gainDb, and optional Q").ThrowAsJavaScriptException();
|
|
952
|
+
return env.Null();
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
float cutoffFreq = info[0].As<Napi::Number>().FloatValue();
|
|
956
|
+
float gainDb = info[1].As<Napi::Number>().FloatValue();
|
|
957
|
+
float Q = 0.707f;
|
|
958
|
+
|
|
959
|
+
if (info.Length() >= 3 && info[2].IsNumber())
|
|
960
|
+
{
|
|
961
|
+
Q = info[2].As<Napi::Number>().FloatValue();
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
try
|
|
965
|
+
{
|
|
966
|
+
auto filter = core::IirFilter<float>::createHighShelf(cutoffFreq, gainDb, Q);
|
|
967
|
+
|
|
968
|
+
auto b_coeffs = filter.getBCoefficients();
|
|
969
|
+
auto a_coeffs = filter.getACoefficients();
|
|
970
|
+
|
|
971
|
+
Napi::Array bArray = Napi::Array::New(env, b_coeffs.size());
|
|
972
|
+
Napi::Array aArray = Napi::Array::New(env, a_coeffs.size());
|
|
973
|
+
|
|
974
|
+
for (size_t i = 0; i < b_coeffs.size(); ++i)
|
|
975
|
+
{
|
|
976
|
+
bArray[i] = Napi::Number::New(env, b_coeffs[i]);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
for (size_t i = 0; i < a_coeffs.size(); ++i)
|
|
980
|
+
{
|
|
981
|
+
aArray[i] = Napi::Number::New(env, a_coeffs[i]);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return constructor.New({bArray, aArray});
|
|
985
|
+
}
|
|
986
|
+
catch (const std::exception &e)
|
|
987
|
+
{
|
|
988
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
989
|
+
return env.Null();
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
// Module initialization
|
|
995
|
+
void InitFilterBindings(Napi::Env env, Napi::Object exports)
|
|
996
|
+
{
|
|
997
|
+
FirFilterWrapper::Init(env, exports);
|
|
998
|
+
IirFilterWrapper::Init(env, exports);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
} // namespace dsp
|