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.
Files changed (172) hide show
  1. package/.github/workflows/ci.yml +185 -0
  2. package/.vscode/c_cpp_properties.json +17 -0
  3. package/.vscode/settings.json +68 -0
  4. package/.vscode/tasks.json +28 -0
  5. package/DISCLAIMER.md +32 -0
  6. package/LICENSE +21 -0
  7. package/README.md +1803 -0
  8. package/ROADMAP.md +192 -0
  9. package/TECHNICAL_DEBT.md +165 -0
  10. package/binding.gyp +65 -0
  11. package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
  12. package/docs/AUTHENTICATION_SECURITY.md +396 -0
  13. package/docs/BACKEND_IMPROVEMENTS.md +399 -0
  14. package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
  15. package/docs/FFT_IMPLEMENTATION.md +490 -0
  16. package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
  17. package/docs/FFT_USER_GUIDE.md +494 -0
  18. package/docs/FILTERS_IMPLEMENTATION.md +260 -0
  19. package/docs/FILTER_API_GUIDE.md +418 -0
  20. package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
  21. package/docs/LOGGER_API_REFERENCE.md +350 -0
  22. package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
  23. package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
  24. package/docs/PHASES_5_7_SUMMARY.md +403 -0
  25. package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
  26. package/docs/SIMD_OPTIMIZATIONS.md +211 -0
  27. package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
  28. package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
  29. package/docs/TIMESERIES_QUICK_REF.md +85 -0
  30. package/docs/advanced.md +559 -0
  31. package/docs/time-series-guide.md +617 -0
  32. package/docs/time-series-migration.md +376 -0
  33. package/jest.config.js +37 -0
  34. package/package.json +42 -0
  35. package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
  36. package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
  37. package/scripts/test.js +24 -0
  38. package/src/build/dsp-ts-redis.node +0 -0
  39. package/src/native/DspPipeline.cc +675 -0
  40. package/src/native/DspPipeline.h +44 -0
  41. package/src/native/FftBindings.cc +817 -0
  42. package/src/native/FilterBindings.cc +1001 -0
  43. package/src/native/IDspStage.h +53 -0
  44. package/src/native/adapters/InterpolatorStage.h +201 -0
  45. package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
  46. package/src/native/adapters/MovingAverageStage.h +306 -0
  47. package/src/native/adapters/RectifyStage.h +88 -0
  48. package/src/native/adapters/ResamplerStage.h +238 -0
  49. package/src/native/adapters/RmsStage.h +299 -0
  50. package/src/native/adapters/SscStage.h +121 -0
  51. package/src/native/adapters/VarianceStage.h +307 -0
  52. package/src/native/adapters/WampStage.h +114 -0
  53. package/src/native/adapters/WaveformLengthStage.h +115 -0
  54. package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
  55. package/src/native/core/FftEngine.cc +441 -0
  56. package/src/native/core/FftEngine.h +224 -0
  57. package/src/native/core/FirFilter.cc +324 -0
  58. package/src/native/core/FirFilter.h +149 -0
  59. package/src/native/core/IirFilter.cc +576 -0
  60. package/src/native/core/IirFilter.h +210 -0
  61. package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
  62. package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
  63. package/src/native/core/MovingAverageFilter.cc +18 -0
  64. package/src/native/core/MovingAverageFilter.h +135 -0
  65. package/src/native/core/MovingFftFilter.cc +291 -0
  66. package/src/native/core/MovingFftFilter.h +203 -0
  67. package/src/native/core/MovingVarianceFilter.cc +194 -0
  68. package/src/native/core/MovingVarianceFilter.h +114 -0
  69. package/src/native/core/MovingZScoreFilter.cc +215 -0
  70. package/src/native/core/MovingZScoreFilter.h +113 -0
  71. package/src/native/core/Policies.h +352 -0
  72. package/src/native/core/RmsFilter.cc +18 -0
  73. package/src/native/core/RmsFilter.h +131 -0
  74. package/src/native/core/SscFilter.cc +16 -0
  75. package/src/native/core/SscFilter.h +137 -0
  76. package/src/native/core/WampFilter.cc +16 -0
  77. package/src/native/core/WampFilter.h +101 -0
  78. package/src/native/core/WaveformLengthFilter.cc +17 -0
  79. package/src/native/core/WaveformLengthFilter.h +98 -0
  80. package/src/native/utils/CircularBufferArray.cc +336 -0
  81. package/src/native/utils/CircularBufferArray.h +62 -0
  82. package/src/native/utils/CircularBufferVector.cc +145 -0
  83. package/src/native/utils/CircularBufferVector.h +45 -0
  84. package/src/native/utils/NapiUtils.cc +53 -0
  85. package/src/native/utils/NapiUtils.h +21 -0
  86. package/src/native/utils/SimdOps.h +870 -0
  87. package/src/native/utils/SlidingWindowFilter.cc +239 -0
  88. package/src/native/utils/SlidingWindowFilter.h +159 -0
  89. package/src/native/utils/TimeSeriesBuffer.cc +205 -0
  90. package/src/native/utils/TimeSeriesBuffer.h +140 -0
  91. package/src/ts/CircularLogBuffer.ts +87 -0
  92. package/src/ts/DriftDetector.ts +331 -0
  93. package/src/ts/TopicRouter.ts +428 -0
  94. package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
  95. package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
  96. package/src/ts/__tests__/Chaining.test.ts +387 -0
  97. package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
  98. package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
  99. package/src/ts/__tests__/DriftDetector.test.ts +389 -0
  100. package/src/ts/__tests__/Fft.test.ts +484 -0
  101. package/src/ts/__tests__/ListState.test.ts +153 -0
  102. package/src/ts/__tests__/Logger.test.ts +208 -0
  103. package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
  104. package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
  105. package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
  106. package/src/ts/__tests__/MovingAverage.test.ts +322 -0
  107. package/src/ts/__tests__/RMS.test.ts +315 -0
  108. package/src/ts/__tests__/Rectify.test.ts +272 -0
  109. package/src/ts/__tests__/Redis.test.ts +456 -0
  110. package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
  111. package/src/ts/__tests__/Tap.test.ts +164 -0
  112. package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
  113. package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
  114. package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
  115. package/src/ts/__tests__/TimeSeries.test.ts +254 -0
  116. package/src/ts/__tests__/TopicRouter.test.ts +332 -0
  117. package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
  118. package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
  119. package/src/ts/__tests__/Variance.test.ts +509 -0
  120. package/src/ts/__tests__/WaveformLength.test.ts +147 -0
  121. package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
  122. package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
  123. package/src/ts/advanced-dsp.ts +566 -0
  124. package/src/ts/backends.ts +1137 -0
  125. package/src/ts/bindings.ts +1225 -0
  126. package/src/ts/easter-egg.ts +42 -0
  127. package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
  128. package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
  129. package/src/ts/examples/MovingAverage/test-state.ts +85 -0
  130. package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
  131. package/src/ts/examples/RMS/test-state.ts +97 -0
  132. package/src/ts/examples/RMS/test-streaming.ts +253 -0
  133. package/src/ts/examples/Rectify/test-state.ts +107 -0
  134. package/src/ts/examples/Rectify/test-streaming.ts +242 -0
  135. package/src/ts/examples/Variance/test-state.ts +195 -0
  136. package/src/ts/examples/Variance/test-streaming.ts +260 -0
  137. package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
  138. package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
  139. package/src/ts/examples/advanced-dsp-examples.ts +397 -0
  140. package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
  141. package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
  142. package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
  143. package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
  144. package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
  145. package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
  146. package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
  147. package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
  148. package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
  149. package/src/ts/examples/chaining/test-chaining.ts +52 -0
  150. package/src/ts/examples/emg-features-example.ts +284 -0
  151. package/src/ts/examples/fft-example.ts +309 -0
  152. package/src/ts/examples/fft-examples.ts +349 -0
  153. package/src/ts/examples/filter-examples.ts +320 -0
  154. package/src/ts/examples/list-state-example.ts +131 -0
  155. package/src/ts/examples/logger-example.ts +91 -0
  156. package/src/ts/examples/notch-filter-examples.ts +243 -0
  157. package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
  158. package/src/ts/examples/phase6-7/production-observability.ts +476 -0
  159. package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
  160. package/src/ts/examples/redis/redis-example.ts +202 -0
  161. package/src/ts/examples/redis-example.ts +202 -0
  162. package/src/ts/examples/simd-benchmark.ts +126 -0
  163. package/src/ts/examples/tap-debugging.ts +230 -0
  164. package/src/ts/examples/timeseries/comparison-example.ts +290 -0
  165. package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
  166. package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
  167. package/src/ts/examples/waveform-length-example.ts +139 -0
  168. package/src/ts/fft.ts +722 -0
  169. package/src/ts/filters.ts +1078 -0
  170. package/src/ts/index.ts +120 -0
  171. package/src/ts/types.ts +589 -0
  172. package/tsconfig.json +15 -0
@@ -0,0 +1,239 @@
1
+ #include "SlidingWindowFilter.h"
2
+ #include "../core/Policies.h"
3
+
4
+ using namespace dsp::utils;
5
+
6
+ // -----------------------------------------------------------------------------
7
+ // Constructor
8
+ // Constructs a new sliding window filter with the specified window size
9
+ // @ param window_size - The number of samples in the sliding window
10
+ // @ param policy - An instance of the policy (default-constructed if not provided)
11
+ // @ return void
12
+ // -----------------------------------------------------------------------------
13
+ template <typename T, typename Policy>
14
+ SlidingWindowFilter<T, Policy>::SlidingWindowFilter(size_t window_size, Policy policy)
15
+ : m_buffer(window_size), m_policy(std::move(policy))
16
+ {
17
+ }
18
+
19
+ // -----------------------------------------------------------------------------
20
+ // Constructor (Time-Aware)
21
+ // Constructs a new time-aware sliding window filter
22
+ // @ param window_size - The number of samples in the sliding window
23
+ // @ param window_duration_ms - The maximum age of samples in milliseconds
24
+ // @ param policy - An instance of the policy (default-constructed if not provided)
25
+ // @ return void
26
+ // -----------------------------------------------------------------------------
27
+ template <typename T, typename Policy>
28
+ SlidingWindowFilter<T, Policy>::SlidingWindowFilter(size_t window_size, double window_duration_ms, Policy policy)
29
+ : m_buffer(window_size, window_duration_ms), m_policy(std::move(policy))
30
+ {
31
+ }
32
+
33
+ // -----------------------------------------------------------------------------
34
+ // addSample
35
+ // Adds a new sample to the sliding window
36
+ // If buffer is full, removes oldest sample (delegates to policy.onRemove)
37
+ // Then adds new sample (delegates to policy.onAdd)
38
+ // @ param newValue - The new sample to add
39
+ // @ return T - The computed result from the policy
40
+ // -----------------------------------------------------------------------------
41
+ template <typename T, typename Policy>
42
+ T SlidingWindowFilter<T, Policy>::addSample(T newValue)
43
+ {
44
+ if (m_buffer.isFull())
45
+ {
46
+ T oldestValue = m_buffer.peek();
47
+ m_policy.onRemove(oldestValue);
48
+ }
49
+
50
+ m_buffer.pushOverwrite(newValue);
51
+ m_policy.onAdd(newValue);
52
+
53
+ return m_policy.getResult(m_buffer.getCount());
54
+ }
55
+
56
+ // -----------------------------------------------------------------------------
57
+ // addSampleWithTimestamp
58
+ // Adds a new sample with timestamp (time-aware mode)
59
+ // Expires old samples first, then adds the new sample
60
+ // @ param newValue - The new sample to add
61
+ // @ param timestamp - The timestamp in milliseconds
62
+ // @ return T - The computed result from the policy
63
+ // -----------------------------------------------------------------------------
64
+ template <typename T, typename Policy>
65
+ T SlidingWindowFilter<T, Policy>::addSampleWithTimestamp(T newValue, double timestamp)
66
+ {
67
+ if (!m_buffer.isTimeAware())
68
+ {
69
+ throw std::runtime_error("addSampleWithTimestamp requires time-aware mode");
70
+ }
71
+
72
+ // First, expire old samples and update policy
73
+ while (m_buffer.getCount() > 0)
74
+ {
75
+ size_t initial_count = m_buffer.getCount();
76
+ m_buffer.expireOld(timestamp);
77
+ size_t expired_count = initial_count - m_buffer.getCount();
78
+
79
+ // We need to reconstruct the policy state from remaining samples
80
+ // This is inefficient but correct. Better approach: track what was removed
81
+ if (expired_count > 0)
82
+ {
83
+ // Rebuild policy from scratch using remaining samples
84
+ m_policy.clear();
85
+ auto remaining = m_buffer.toVector();
86
+ for (const auto &value : remaining)
87
+ {
88
+ m_policy.onAdd(value);
89
+ }
90
+ }
91
+ break; // expireOld already removed all expired samples
92
+ }
93
+
94
+ // Check if buffer is full (by sample count)
95
+ if (m_buffer.isFull())
96
+ {
97
+ T oldestValue = m_buffer.peek();
98
+ m_policy.onRemove(oldestValue);
99
+ }
100
+
101
+ // Add the new sample
102
+ m_buffer.pushOverwriteWithTimestamp(newValue, timestamp);
103
+ m_policy.onAdd(newValue);
104
+
105
+ return m_policy.getResult(m_buffer.getCount());
106
+ }
107
+
108
+ // -----------------------------------------------------------------------------
109
+ // clear
110
+ // Clears all samples from the filter
111
+ // Resets both the circular buffer and the policy state
112
+ // @ return void
113
+ // -----------------------------------------------------------------------------
114
+ template <typename T, typename Policy>
115
+ void SlidingWindowFilter<T, Policy>::clear()
116
+ {
117
+ m_buffer.clear();
118
+ m_policy.clear();
119
+ }
120
+
121
+ // -----------------------------------------------------------------------------
122
+ // isFull
123
+ // Checks if the buffer is full
124
+ // @ return bool - true if buffer contains window_size samples
125
+ // -----------------------------------------------------------------------------
126
+ template <typename T, typename Policy>
127
+ bool SlidingWindowFilter<T, Policy>::isFull() const noexcept
128
+ {
129
+ return m_buffer.isFull();
130
+ }
131
+
132
+ // -----------------------------------------------------------------------------
133
+ // getCount
134
+ // Gets the current number of samples in the buffer
135
+ // @ return size_t - The number of samples
136
+ // -----------------------------------------------------------------------------
137
+ template <typename T, typename Policy>
138
+ size_t SlidingWindowFilter<T, Policy>::getCount() const noexcept
139
+ {
140
+ return m_buffer.getCount();
141
+ }
142
+
143
+ // -----------------------------------------------------------------------------
144
+ // getWindowSize
145
+ // Gets the window size
146
+ // @ return size_t - The maximum number of samples in the window
147
+ // -----------------------------------------------------------------------------
148
+ template <typename T, typename Policy>
149
+ size_t SlidingWindowFilter<T, Policy>::getWindowSize() const noexcept
150
+ {
151
+ return m_buffer.getCapacity();
152
+ }
153
+
154
+ // -----------------------------------------------------------------------------
155
+ // getBufferContents
156
+ // Exports the buffer contents
157
+ // @ return std::vector<T> - A vector containing all samples in the buffer
158
+ // -----------------------------------------------------------------------------
159
+ template <typename T, typename Policy>
160
+ std::vector<T> SlidingWindowFilter<T, Policy>::getBufferContents() const
161
+ {
162
+ return m_buffer.toVector();
163
+ }
164
+
165
+ // -----------------------------------------------------------------------------
166
+ // setBufferContents
167
+ // Restores the buffer contents
168
+ // @ param bufferData - The samples to restore to the buffer
169
+ // @ return void
170
+ // -----------------------------------------------------------------------------
171
+ template <typename T, typename Policy>
172
+ void SlidingWindowFilter<T, Policy>::setBufferContents(const std::vector<T> &bufferData)
173
+ {
174
+ m_buffer.fromVector(bufferData);
175
+ }
176
+
177
+ // -----------------------------------------------------------------------------
178
+ // getPolicy
179
+ // Access to the policy for state serialization
180
+ // @ return Policy& - Reference to the internal policy object
181
+ // -----------------------------------------------------------------------------
182
+ template <typename T, typename Policy>
183
+ Policy &SlidingWindowFilter<T, Policy>::getPolicy()
184
+ {
185
+ return m_policy;
186
+ }
187
+
188
+ // -----------------------------------------------------------------------------
189
+ // getPolicy (const)
190
+ // Const access to the policy
191
+ // @ return const Policy& - Const reference to the internal policy object
192
+ // -----------------------------------------------------------------------------
193
+ template <typename T, typename Policy>
194
+ const Policy &SlidingWindowFilter<T, Policy>::getPolicy() const
195
+ {
196
+ return m_policy;
197
+ }
198
+
199
+ // Note: getState() and setState() are template methods defined in the header
200
+ // They use decltype and are auto-deduced, so they must remain in the header
201
+
202
+ // -----------------------------------------------------------------------------
203
+ // Explicit template instantiations
204
+ // Instantiate all Policy combinations we use
205
+ // -----------------------------------------------------------------------------
206
+ namespace dsp::utils
207
+ {
208
+ using namespace dsp::core;
209
+
210
+ // MeanPolicy instantiations
211
+ template class SlidingWindowFilter<int, MeanPolicy<int>>;
212
+ template class SlidingWindowFilter<float, MeanPolicy<float>>;
213
+ template class SlidingWindowFilter<double, MeanPolicy<double>>;
214
+
215
+ // RmsPolicy instantiations
216
+ template class SlidingWindowFilter<int, RmsPolicy<int>>;
217
+ template class SlidingWindowFilter<float, RmsPolicy<float>>;
218
+ template class SlidingWindowFilter<double, RmsPolicy<double>>;
219
+
220
+ // MeanAbsoluteValuePolicy instantiations
221
+ template class SlidingWindowFilter<float, MeanAbsoluteValuePolicy<float>>;
222
+ template class SlidingWindowFilter<double, MeanAbsoluteValuePolicy<double>>;
223
+
224
+ // VariancePolicy instantiations
225
+ template class SlidingWindowFilter<int, VariancePolicy<int>>;
226
+ template class SlidingWindowFilter<float, VariancePolicy<float>>;
227
+ template class SlidingWindowFilter<double, VariancePolicy<double>>;
228
+
229
+ // Note: ZScorePolicy is NOT instantiated here because it has a different interface
230
+ // (getResult takes 2 parameters: currentValue and count, not just count)
231
+ // MovingZScoreFilter doesn't use SlidingWindowFilter template
232
+
233
+ // WaveformLengthFilter instantiations
234
+ template class dsp::utils::SlidingWindowFilter<float, SumPolicy<float>>;
235
+ template class dsp::utils::SlidingWindowFilter<double, SumPolicy<double>>;
236
+
237
+ // WAMP,SSC instantiations
238
+ template class dsp::utils::SlidingWindowFilter<bool, CounterPolicy>;
239
+ }
@@ -0,0 +1,159 @@
1
+ #pragma once
2
+ #include "CircularBufferArray.h"
3
+ #include <utility>
4
+ #include <vector>
5
+
6
+ namespace dsp::utils
7
+ {
8
+ /**
9
+ * @brief A generic, policy-based sliding window filter engine.
10
+ *
11
+ * This class implements the sliding window logic (circular buffer management)
12
+ * while delegating the statistical computation to a Policy class.
13
+ *
14
+ * This is a zero-cost abstraction: the compiler inlines all policy methods,
15
+ * resulting in performance identical to hand-written specialized filters.
16
+ *
17
+ * @tparam T The numeric type (e.g., float, double).
18
+ * @tparam Policy The struct that defines the state and math operations.
19
+ * Must implement: onAdd(T), onRemove(T), clear(), getResult(size_t)
20
+ */
21
+ template <typename T, typename Policy>
22
+ class SlidingWindowFilter
23
+ {
24
+ public:
25
+ /**
26
+ * @brief Constructs a new sliding window filter.
27
+ * @param window_size The number of samples in the sliding window.
28
+ * @param policy An instance of the policy (default-constructed if not provided).
29
+ */
30
+ explicit SlidingWindowFilter(size_t window_size, Policy policy = Policy());
31
+
32
+ /**
33
+ * @brief Constructs a new time-aware sliding window filter.
34
+ * @param window_size The number of samples in the sliding window.
35
+ * @param window_duration_ms The maximum age of samples in milliseconds.
36
+ * @param policy An instance of the policy (default-constructed if not provided).
37
+ */
38
+ explicit SlidingWindowFilter(size_t window_size, double window_duration_ms, Policy policy = Policy());
39
+
40
+ // Delete copy constructor and copy assignment
41
+ SlidingWindowFilter(const SlidingWindowFilter &) = delete;
42
+ SlidingWindowFilter &operator=(const SlidingWindowFilter &) = delete;
43
+
44
+ // Enable move semantics
45
+ SlidingWindowFilter(SlidingWindowFilter &&) noexcept = default;
46
+ SlidingWindowFilter &operator=(SlidingWindowFilter &&) noexcept = default;
47
+
48
+ /**
49
+ * @brief Adds a new sample to the sliding window.
50
+ *
51
+ * If the buffer is full, removes the oldest sample (delegates to policy.onRemove),
52
+ * then adds the new sample (delegates to policy.onAdd).
53
+ *
54
+ * @param newValue The new sample to add.
55
+ * @return T The computed result from the policy.
56
+ */
57
+ T addSample(T newValue);
58
+
59
+ /**
60
+ * @brief Adds a new sample with timestamp (time-aware mode).
61
+ *
62
+ * Expires old samples first, then adds the new sample.
63
+ *
64
+ * @param newValue The new sample to add.
65
+ * @param timestamp The timestamp in milliseconds.
66
+ * @return T The computed result from the policy.
67
+ */
68
+ T addSampleWithTimestamp(T newValue, double timestamp);
69
+
70
+ /**
71
+ * @brief Clears all samples from the filter.
72
+ *
73
+ * Resets both the circular buffer and the policy state.
74
+ */
75
+ void clear();
76
+
77
+ /**
78
+ * @brief Checks if the buffer is full.
79
+ * @return true if the buffer contains window_size samples.
80
+ */
81
+ bool isFull() const noexcept;
82
+
83
+ /**
84
+ * @brief Gets the current number of samples in the buffer.
85
+ * @return size_t The number of samples.
86
+ */
87
+ size_t getCount() const noexcept;
88
+
89
+ /**
90
+ * @brief Gets the window size.
91
+ * @return size_t The maximum number of samples in the window.
92
+ */
93
+ size_t getWindowSize() const noexcept;
94
+
95
+ /**
96
+ * @brief Checks if this is a time-aware filter.
97
+ * @return bool True if window duration is set.
98
+ */
99
+ bool isTimeAware() const noexcept { return m_buffer.isTimeAware(); }
100
+
101
+ /**
102
+ * @brief Exports the buffer contents.
103
+ * @return std::vector<T> A vector containing all samples in the buffer.
104
+ */
105
+ std::vector<T> getBufferContents() const;
106
+
107
+ /**
108
+ * @brief Restores the buffer contents.
109
+ * @param bufferData The samples to restore to the buffer.
110
+ */
111
+ void setBufferContents(const std::vector<T> &bufferData);
112
+
113
+ /**
114
+ * @brief Access to the policy for state serialization.
115
+ * @return Policy& Reference to the internal policy object.
116
+ */
117
+ Policy &getPolicy();
118
+
119
+ /**
120
+ * @brief Const access to the policy.
121
+ * @return const Policy& Const reference to the internal policy object.
122
+ */
123
+ const Policy &getPolicy() const;
124
+
125
+ /**
126
+ * @brief Exports the complete filter state (buffer + policy state).
127
+ *
128
+ * This is a generic state serialization method that works with any policy
129
+ * that implements getState(). Returns a pair of buffer contents and policy state.
130
+ *
131
+ * @return std::pair<std::vector<T>, PolicyState> Buffer contents and policy state.
132
+ */
133
+ auto getState() const -> std::pair<std::vector<T>, decltype(std::declval<Policy>().getState())>
134
+ {
135
+ return {m_buffer.toVector(), m_policy.getState()};
136
+ }
137
+
138
+ /**
139
+ * @brief Restores the complete filter state (buffer + policy state).
140
+ *
141
+ * This is a generic state deserialization method that works with any policy
142
+ * that implements setState(). Restores both buffer contents and policy state.
143
+ *
144
+ * @param bufferData The buffer contents to restore.
145
+ * @param policyState The policy state to restore (type depends on policy).
146
+ */
147
+ template <typename PolicyState>
148
+ void setState(const std::vector<T> &bufferData, const PolicyState &policyState)
149
+ {
150
+ m_buffer.fromVector(bufferData);
151
+ m_policy.setState(policyState);
152
+ }
153
+
154
+ private:
155
+ CircularBufferArray<T> m_buffer;
156
+ Policy m_policy;
157
+ };
158
+
159
+ } // namespace dsp::utils
@@ -0,0 +1,205 @@
1
+ #include "TimeSeriesBuffer.h"
2
+
3
+ namespace dsp::utils
4
+ {
5
+ // -----------------------------------------------------------------------------
6
+ // Constructor
7
+ // -----------------------------------------------------------------------------
8
+ template <typename T>
9
+ TimeSeriesBuffer<T>::TimeSeriesBuffer(size_t max_samples, uint64_t window_duration_ms)
10
+ : m_max_samples(max_samples), m_window_duration_ms(window_duration_ms)
11
+ {
12
+ }
13
+
14
+ // -----------------------------------------------------------------------------
15
+ // push - Adds a new timestamped sample
16
+ // Automatically enforces window constraints after insertion
17
+ // -----------------------------------------------------------------------------
18
+ template <typename T>
19
+ void TimeSeriesBuffer<T>::push(TimestampType timestamp, T value)
20
+ {
21
+ m_samples.emplace_back(timestamp, value);
22
+ enforceWindowConstraints();
23
+ }
24
+
25
+ // -----------------------------------------------------------------------------
26
+ // removeOlderThan - Removes samples older than cutoff timestamp
27
+ // -----------------------------------------------------------------------------
28
+ template <typename T>
29
+ size_t TimeSeriesBuffer<T>::removeOlderThan(TimestampType cutoff_timestamp)
30
+ {
31
+ size_t removed = 0;
32
+ while (!m_samples.empty() && m_samples.front().first < cutoff_timestamp)
33
+ {
34
+ m_samples.pop_front();
35
+ ++removed;
36
+ }
37
+ return removed;
38
+ }
39
+
40
+ // -----------------------------------------------------------------------------
41
+ // front - Gets the oldest sample (does not remove)
42
+ // -----------------------------------------------------------------------------
43
+ template <typename T>
44
+ const typename TimeSeriesBuffer<T>::Sample &TimeSeriesBuffer<T>::front() const
45
+ {
46
+ if (m_samples.empty())
47
+ {
48
+ throw std::out_of_range("TimeSeriesBuffer::front() called on empty buffer");
49
+ }
50
+ return m_samples.front();
51
+ }
52
+
53
+ // -----------------------------------------------------------------------------
54
+ // back - Gets the newest sample (does not remove)
55
+ // -----------------------------------------------------------------------------
56
+ template <typename T>
57
+ const typename TimeSeriesBuffer<T>::Sample &TimeSeriesBuffer<T>::back() const
58
+ {
59
+ if (m_samples.empty())
60
+ {
61
+ throw std::out_of_range("TimeSeriesBuffer::back() called on empty buffer");
62
+ }
63
+ return m_samples.back();
64
+ }
65
+
66
+ // -----------------------------------------------------------------------------
67
+ // popFront - Removes the oldest sample
68
+ // -----------------------------------------------------------------------------
69
+ template <typename T>
70
+ void TimeSeriesBuffer<T>::popFront()
71
+ {
72
+ if (m_samples.empty())
73
+ {
74
+ throw std::out_of_range("TimeSeriesBuffer::popFront() called on empty buffer");
75
+ }
76
+ m_samples.pop_front();
77
+ }
78
+
79
+ // -----------------------------------------------------------------------------
80
+ // size - Returns the current number of samples
81
+ // -----------------------------------------------------------------------------
82
+ template <typename T>
83
+ size_t TimeSeriesBuffer<T>::size() const noexcept
84
+ {
85
+ return m_samples.size();
86
+ }
87
+
88
+ // -----------------------------------------------------------------------------
89
+ // empty - Checks if buffer is empty
90
+ // -----------------------------------------------------------------------------
91
+ template <typename T>
92
+ bool TimeSeriesBuffer<T>::empty() const noexcept
93
+ {
94
+ return m_samples.empty();
95
+ }
96
+
97
+ // -----------------------------------------------------------------------------
98
+ // clear - Removes all samples
99
+ // -----------------------------------------------------------------------------
100
+ template <typename T>
101
+ void TimeSeriesBuffer<T>::clear() noexcept
102
+ {
103
+ m_samples.clear();
104
+ }
105
+
106
+ // -----------------------------------------------------------------------------
107
+ // toVector - Exports all samples as a vector
108
+ // -----------------------------------------------------------------------------
109
+ template <typename T>
110
+ std::vector<typename TimeSeriesBuffer<T>::Sample> TimeSeriesBuffer<T>::toVector() const
111
+ {
112
+ return std::vector<Sample>(m_samples.begin(), m_samples.end());
113
+ }
114
+
115
+ // -----------------------------------------------------------------------------
116
+ // fromVector - Restores buffer from vector
117
+ // -----------------------------------------------------------------------------
118
+ template <typename T>
119
+ void TimeSeriesBuffer<T>::fromVector(const std::vector<Sample> &samples)
120
+ {
121
+ m_samples.clear();
122
+ for (const auto &sample : samples)
123
+ {
124
+ m_samples.push_back(sample);
125
+ }
126
+ }
127
+
128
+ // -----------------------------------------------------------------------------
129
+ // getTimeSpan - Returns time difference between newest and oldest sample
130
+ // -----------------------------------------------------------------------------
131
+ template <typename T>
132
+ uint64_t TimeSeriesBuffer<T>::getTimeSpan() const noexcept
133
+ {
134
+ if (m_samples.size() < 2)
135
+ {
136
+ return 0;
137
+ }
138
+ return m_samples.back().first - m_samples.front().first;
139
+ }
140
+
141
+ // -----------------------------------------------------------------------------
142
+ // begin/end - Iterator access
143
+ // -----------------------------------------------------------------------------
144
+ template <typename T>
145
+ typename std::deque<typename TimeSeriesBuffer<T>::Sample>::const_iterator
146
+ TimeSeriesBuffer<T>::begin() const noexcept
147
+ {
148
+ return m_samples.begin();
149
+ }
150
+
151
+ template <typename T>
152
+ typename std::deque<typename TimeSeriesBuffer<T>::Sample>::const_iterator
153
+ TimeSeriesBuffer<T>::end() const noexcept
154
+ {
155
+ return m_samples.end();
156
+ }
157
+
158
+ // -----------------------------------------------------------------------------
159
+ // getMaxSamples - Returns the max sample count constraint
160
+ // -----------------------------------------------------------------------------
161
+ template <typename T>
162
+ size_t TimeSeriesBuffer<T>::getMaxSamples() const noexcept
163
+ {
164
+ return m_max_samples;
165
+ }
166
+
167
+ // -----------------------------------------------------------------------------
168
+ // getWindowDuration - Returns the time window constraint
169
+ // -----------------------------------------------------------------------------
170
+ template <typename T>
171
+ uint64_t TimeSeriesBuffer<T>::getWindowDuration() const noexcept
172
+ {
173
+ return m_window_duration_ms;
174
+ }
175
+
176
+ // -----------------------------------------------------------------------------
177
+ // enforceWindowConstraints - Private helper to maintain window limits
178
+ // Called automatically after each push()
179
+ // -----------------------------------------------------------------------------
180
+ template <typename T>
181
+ void TimeSeriesBuffer<T>::enforceWindowConstraints()
182
+ {
183
+ // Enforce time-based window (if enabled)
184
+ if (m_window_duration_ms > 0 && m_samples.size() > 1)
185
+ {
186
+ TimestampType newest_timestamp = m_samples.back().first;
187
+ TimestampType cutoff = newest_timestamp - m_window_duration_ms;
188
+ removeOlderThan(cutoff);
189
+ }
190
+
191
+ // Enforce sample-count window (if enabled)
192
+ while (m_max_samples > 0 && m_samples.size() > m_max_samples)
193
+ {
194
+ m_samples.pop_front();
195
+ }
196
+ }
197
+
198
+ // -----------------------------------------------------------------------------
199
+ // Explicit template instantiations
200
+ // -----------------------------------------------------------------------------
201
+ template class TimeSeriesBuffer<int>;
202
+ template class TimeSeriesBuffer<float>;
203
+ template class TimeSeriesBuffer<double>;
204
+
205
+ } // namespace dsp::utils