dspx 1.1.6 → 1.2.4
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/dist/bindings.d.ts +33 -10
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +68 -12
- package/dist/bindings.js.map +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/prebuilds/win32-x64/dspx.node +0 -0
- package/src/native/DspPipeline.cc +132 -27
- package/src/native/adapters/FilterStage.cc +17 -1
- package/src/native/adapters/PeakDetectionStage.h +181 -68
- package/src/native/adapters/RmsStage.h +1 -1
- package/src/native/adapters/VarianceStage.h +1 -1
- package/src/native/adapters/ZScoreNormalizeStage.h +1 -1
- package/src/native/core/PeakDetection.h +225 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <cstddef>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include <algorithm>
|
|
7
|
+
#include "../utils/SimdOps.h"
|
|
8
|
+
|
|
9
|
+
namespace dsp::core
|
|
10
|
+
{
|
|
11
|
+
/**
|
|
12
|
+
* @brief Post-processing pass to enforce minimum peak distance.
|
|
13
|
+
* Modifies the output buffer in-place.
|
|
14
|
+
*/
|
|
15
|
+
inline void apply_min_peak_distance(float *output, size_t size, int minPeakDistance)
|
|
16
|
+
{
|
|
17
|
+
if (minPeakDistance <= 1)
|
|
18
|
+
{
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
int cooldown = 0;
|
|
23
|
+
for (size_t i = 0; i < size; ++i)
|
|
24
|
+
{
|
|
25
|
+
if (output[i] == 1.0f)
|
|
26
|
+
{
|
|
27
|
+
if (cooldown == 0)
|
|
28
|
+
{
|
|
29
|
+
// This is a valid peak, start cooldown
|
|
30
|
+
cooldown = minPeakDistance - 1;
|
|
31
|
+
}
|
|
32
|
+
else
|
|
33
|
+
{
|
|
34
|
+
// This peak is suppressed
|
|
35
|
+
output[i] = 0.0f;
|
|
36
|
+
cooldown--; // Still decrement cooldown
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (cooldown > 0)
|
|
40
|
+
{
|
|
41
|
+
cooldown--;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @brief Scalar (flexible) delayed peak detection.
|
|
48
|
+
* This is the fallback for windowSize != 3.
|
|
49
|
+
*/
|
|
50
|
+
inline void find_peaks_batch_scalar_delayed(const float *input, float *output, size_t size, float threshold, int windowSize)
|
|
51
|
+
{
|
|
52
|
+
// We check a peak at [i-k] when we see sample [i].
|
|
53
|
+
// This maintains the "delayed" logic.
|
|
54
|
+
const int k = (windowSize - 1) / 2;
|
|
55
|
+
const int end_idx = windowSize - 1;
|
|
56
|
+
|
|
57
|
+
if (size < windowSize)
|
|
58
|
+
{
|
|
59
|
+
return; // Not enough samples
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (size_t i = end_idx; i < size; ++i)
|
|
63
|
+
{
|
|
64
|
+
const size_t peak_idx = i - k; // The candidate peak index
|
|
65
|
+
const float candidate = input[peak_idx];
|
|
66
|
+
|
|
67
|
+
if (candidate < threshold)
|
|
68
|
+
{
|
|
69
|
+
output[peak_idx] = 0.0f;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
bool is_max = true;
|
|
74
|
+
// Check all neighbors from [i-k-k] to [i-k+k] (which is [i-end_idx] to [i])
|
|
75
|
+
for (int j = -k; j <= k; ++j)
|
|
76
|
+
{
|
|
77
|
+
if (j == 0)
|
|
78
|
+
continue; // Don't compare to self
|
|
79
|
+
|
|
80
|
+
if (candidate <= input[peak_idx + j])
|
|
81
|
+
{
|
|
82
|
+
is_max = false;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
output[peak_idx] = is_max ? 1.0f : 0.0f;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @brief Stateless (batch) peak detection using the 3-point delayed method.
|
|
93
|
+
*
|
|
94
|
+
* Finds local maxima in a buffer.
|
|
95
|
+
*
|
|
96
|
+
* @param input The input signal buffer.
|
|
97
|
+
* @param output The output buffer (will be filled with 1.0 at peaks, 0.0 otherwise).
|
|
98
|
+
* @param size The number of samples in the input/output buffers.
|
|
99
|
+
* @param threshold The minimum value for a peak to be considered.
|
|
100
|
+
* @param windowSize The local neighborhood size (must be odd, >= 3).
|
|
101
|
+
* @param minPeakDistance The minimum samples between peaks.
|
|
102
|
+
*
|
|
103
|
+
* @note Output edges (0..k and size-k..size) will be 0.
|
|
104
|
+
*/
|
|
105
|
+
inline void find_peaks_batch_delayed(const float *input, float *output, size_t size, float threshold, int windowSize, int minPeakDistance)
|
|
106
|
+
{
|
|
107
|
+
// Initialize output to 0.0
|
|
108
|
+
std::fill(output, output + size, 0.0f);
|
|
109
|
+
|
|
110
|
+
if (windowSize < 3 || windowSize % 2 == 0)
|
|
111
|
+
{
|
|
112
|
+
return; // Invalid window size
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (size < windowSize)
|
|
116
|
+
{
|
|
117
|
+
return; // Not enough samples
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (windowSize == 3)
|
|
121
|
+
{
|
|
122
|
+
// --- Optimized SIMD Path for windowSize = 3 ---
|
|
123
|
+
size_t i = 2;
|
|
124
|
+
|
|
125
|
+
#if defined(SIMD_AVX2)
|
|
126
|
+
const size_t simd_width = 8;
|
|
127
|
+
const size_t simd_limit = (size >= simd_width + 2) ? (size - simd_width) : 0;
|
|
128
|
+
const __m256 v_thresh = _mm256_set1_ps(threshold);
|
|
129
|
+
const __m256 v_ones = _mm256_set1_ps(1.0f);
|
|
130
|
+
|
|
131
|
+
for (i = 2; i <= simd_limit; i += simd_width)
|
|
132
|
+
{
|
|
133
|
+
__m256 v_prev_prev = _mm256_loadu_ps(&input[i - 2]);
|
|
134
|
+
__m256 v_prev = _mm256_loadu_ps(&input[i - 1]);
|
|
135
|
+
__m256 v_current = _mm256_loadu_ps(&input[i]);
|
|
136
|
+
|
|
137
|
+
__m256 mask1 = _mm256_cmp_ps(v_prev, v_prev_prev, _CMP_GT_OQ);
|
|
138
|
+
__m256 mask2 = _mm256_cmp_ps(v_prev, v_current, _CMP_GT_OQ);
|
|
139
|
+
__m256 mask3 = _mm256_cmp_ps(v_prev, v_thresh, _CMP_GE_OQ);
|
|
140
|
+
|
|
141
|
+
__m256 mask = _mm256_and_ps(_mm256_and_ps(mask1, mask2), mask3);
|
|
142
|
+
__m256 v_result = _mm256_and_ps(mask, v_ones);
|
|
143
|
+
_mm256_storeu_ps(&output[i - 1], v_result);
|
|
144
|
+
}
|
|
145
|
+
#elif defined(SIMD_SSE2)
|
|
146
|
+
// ... (Same SSE2 logic as before) ...
|
|
147
|
+
const size_t simd_width = 4;
|
|
148
|
+
const size_t simd_limit = (size >= simd_width + 2) ? (size - simd_width) : 0;
|
|
149
|
+
const __m128 v_thresh = _mm_set1_ps(threshold);
|
|
150
|
+
const __m128 v_ones = _mm_set1_ps(1.0f);
|
|
151
|
+
|
|
152
|
+
for (i = 2; i <= simd_limit; i += simd_width)
|
|
153
|
+
{
|
|
154
|
+
__m128 v_prev_prev = _mm_loadu_ps(&input[i - 2]);
|
|
155
|
+
__m128 v_prev = _mm_loadu_ps(&input[i - 1]);
|
|
156
|
+
__m128 v_current = _mm_loadu_ps(&input[i]);
|
|
157
|
+
|
|
158
|
+
__m128 mask1 = _mm_cmpgt_ps(v_prev, v_prev_prev);
|
|
159
|
+
__m128 mask2 = _mm_cmpgt_ps(v_prev, v_current);
|
|
160
|
+
__m128 mask3 = _mm_cmpge_ps(v_prev, v_thresh);
|
|
161
|
+
|
|
162
|
+
__m128 mask = _mm_and_ps(_mm_and_ps(mask1, mask2), mask3);
|
|
163
|
+
__m128 v_result = _mm_and_ps(mask, v_ones);
|
|
164
|
+
_mm_storeu_ps(&output[i - 1], v_result);
|
|
165
|
+
}
|
|
166
|
+
#elif defined(SIMD_NEON)
|
|
167
|
+
// ... (Same NEON logic as before) ...
|
|
168
|
+
const size_t simd_width = 4;
|
|
169
|
+
const size_t simd_limit = (size >= simd_width + 2) ? (size - simd_width) : 0;
|
|
170
|
+
const float32x4_t v_thresh = vdupq_n_f32(threshold);
|
|
171
|
+
const float32x4_t v_ones = vdupq_n_f32(1.0f);
|
|
172
|
+
const float32x4_t v_zeros = vdupq_n_f32(0.0f);
|
|
173
|
+
|
|
174
|
+
for (i = 2; i <= simd_limit; i += simd_width)
|
|
175
|
+
{
|
|
176
|
+
float32x4_t v_prev_prev = vld1q_f32(&input[i - 2]);
|
|
177
|
+
float32x4_t v_prev = vld1q_f32(&input[i - 1]);
|
|
178
|
+
float32x4_t v_current = vld1q_f32(&input[i]);
|
|
179
|
+
|
|
180
|
+
uint32x4_t mask1 = vcgtq_f32(v_prev, v_prev_prev);
|
|
181
|
+
uint32x4_t mask2 = vcgtq_f32(v_prev, v_current);
|
|
182
|
+
uint32x4_t mask3 = vcgeq_f32(v_prev, v_thresh);
|
|
183
|
+
|
|
184
|
+
uint32x4_t mask = vandq_u32(vandq_u32(mask1, mask2), mask3);
|
|
185
|
+
float32x4_t v_result = vbslq_f32(mask, v_ones, v_zeros);
|
|
186
|
+
vst1q_f32(&output[i - 1], v_result);
|
|
187
|
+
}
|
|
188
|
+
#endif
|
|
189
|
+
// Handle remainder for windowSize = 3
|
|
190
|
+
for (; i < size; ++i)
|
|
191
|
+
{
|
|
192
|
+
const float prev_prev = input[i - 2];
|
|
193
|
+
const float prev = input[i - 1];
|
|
194
|
+
const float current = input[i];
|
|
195
|
+
|
|
196
|
+
bool prev_is_peak = (prev > prev_prev) && (prev > current) && (prev >= threshold);
|
|
197
|
+
if (prev_is_peak)
|
|
198
|
+
{
|
|
199
|
+
output[i - 1] = 1.0f;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else
|
|
204
|
+
{
|
|
205
|
+
// --- Scalar Fallback Path for windowSize != 3 ---
|
|
206
|
+
find_peaks_batch_scalar_delayed(input, output, size, threshold, windowSize);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- Post-processing for Minimum Peak Distance ---
|
|
210
|
+
if (minPeakDistance > 1)
|
|
211
|
+
{
|
|
212
|
+
apply_min_peak_distance(output, size, minPeakDistance);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @brief Stateless (batch) peak detection for frequency domain.
|
|
218
|
+
*/
|
|
219
|
+
inline void find_freq_peaks_batch(const float *input, float *output, size_t size, float threshold, int windowSize, int minPeakDistance)
|
|
220
|
+
{
|
|
221
|
+
// The algorithm is identical to the time-domain batch version.
|
|
222
|
+
find_peaks_batch_delayed(input, output, size, threshold, windowSize, minPeakDistance);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
} // namespace dsp::core
|