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,101 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../utils/SlidingWindowFilter.h"
|
|
4
|
+
#include "Policies.h"
|
|
5
|
+
#include <cmath> // For std::abs
|
|
6
|
+
#include <utility> // For std::pair
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
namespace dsp::core
|
|
10
|
+
{
|
|
11
|
+
template <typename T>
|
|
12
|
+
class WampFilter
|
|
13
|
+
{
|
|
14
|
+
public:
|
|
15
|
+
/**
|
|
16
|
+
* @brief Constructs a new Wamp Filter.
|
|
17
|
+
* @param window_size The number of samples to consider in the sliding window.
|
|
18
|
+
* @param threshold The amplitude change threshold to detect.
|
|
19
|
+
*/
|
|
20
|
+
explicit WampFilter(size_t window_size, T threshold)
|
|
21
|
+
: m_filter(window_size), m_threshold(threshold), m_previous_sample(0.0), m_is_initialized(false) {}
|
|
22
|
+
|
|
23
|
+
// Delete copy constructor and copy assignment
|
|
24
|
+
WampFilter(const WampFilter &) = delete;
|
|
25
|
+
WampFilter &operator=(const WampFilter &) = delete;
|
|
26
|
+
|
|
27
|
+
// Enable move semantics
|
|
28
|
+
WampFilter(WampFilter &&) noexcept = default;
|
|
29
|
+
WampFilter &operator=(WampFilter &&) noexcept = default;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @brief Adds a new sample to the filter.
|
|
33
|
+
* @param newValue The new sample value to add.
|
|
34
|
+
* @return T The updated WAMP count over the window.
|
|
35
|
+
*/
|
|
36
|
+
T addSample(T newValue)
|
|
37
|
+
{
|
|
38
|
+
bool did_exceed = false;
|
|
39
|
+
if (m_is_initialized)
|
|
40
|
+
{
|
|
41
|
+
did_exceed = std::abs(newValue - m_previous_sample) > m_threshold;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
m_previous_sample = newValue;
|
|
45
|
+
m_is_initialized = true;
|
|
46
|
+
|
|
47
|
+
// Add the boolean to the window, get the count back
|
|
48
|
+
// Cast count (size_t) to T (float/double)
|
|
49
|
+
return static_cast<T>(m_filter.addSample(did_exceed));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @brief Clears all samples from the filter and resets state.
|
|
54
|
+
*/
|
|
55
|
+
void clear()
|
|
56
|
+
{
|
|
57
|
+
m_filter.clear();
|
|
58
|
+
m_previous_sample = 0.0;
|
|
59
|
+
m_is_initialized = false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @brief Exports the complete filter state (buffer + previous sample).
|
|
64
|
+
* @return A pair containing the buffer contents and previous sample.
|
|
65
|
+
*/
|
|
66
|
+
auto getState() const
|
|
67
|
+
{
|
|
68
|
+
return std::make_pair(m_filter.getState(), m_previous_sample);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @brief Restores the complete filter state (buffer + previous sample).
|
|
73
|
+
* @param buffer The buffer contents to restore.
|
|
74
|
+
* @param count The count of 'true' entries in the buffer.
|
|
75
|
+
* @param prevSample The previous sample value to restore.
|
|
76
|
+
* @return void
|
|
77
|
+
*/
|
|
78
|
+
void setState(const std::vector<bool> &buffer, size_t count, T prevSample)
|
|
79
|
+
{
|
|
80
|
+
m_filter.setState(buffer, count);
|
|
81
|
+
m_previous_sample = prevSample;
|
|
82
|
+
m_is_initialized = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @brief Gets const access to the internal SlidingWindowFilter.
|
|
87
|
+
* @return const SlidingWindowFilter<bool, CounterPolicy>& Reference to the internal filter
|
|
88
|
+
*/
|
|
89
|
+
const dsp::utils::SlidingWindowFilter<bool, CounterPolicy> &getInternalFilter() const
|
|
90
|
+
{
|
|
91
|
+
return m_filter;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private:
|
|
95
|
+
dsp::utils::SlidingWindowFilter<bool, CounterPolicy> m_filter;
|
|
96
|
+
T m_threshold;
|
|
97
|
+
T m_previous_sample;
|
|
98
|
+
bool m_is_initialized;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
} // namespace dsp::core
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file WaveformLengthFilter.cc
|
|
3
|
+
* @brief Implementation file for WaveformLengthFilter (now header-only with policy-based design)
|
|
4
|
+
* This file now only contains explicit template instantiations.
|
|
5
|
+
* The actual implementation is in the header file WaveformLengthFilter.h,
|
|
6
|
+
* which delegates to SlidingWindowFilter<T, SumPolicy<T>>.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#include "WaveformLengthFilter.h"
|
|
10
|
+
|
|
11
|
+
// Explicit template instantiation for float type
|
|
12
|
+
namespace dsp::core
|
|
13
|
+
{
|
|
14
|
+
template class WaveformLengthFilter<float>;
|
|
15
|
+
template class WaveformLengthFilter<double>;
|
|
16
|
+
|
|
17
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../utils/SlidingWindowFilter.h"
|
|
4
|
+
#include "Policies.h"
|
|
5
|
+
#include <cmath> // For std::abs
|
|
6
|
+
#include <utility> // For std::pair
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
namespace dsp::core
|
|
10
|
+
{
|
|
11
|
+
template <typename T>
|
|
12
|
+
class WaveformLengthFilter
|
|
13
|
+
{
|
|
14
|
+
public:
|
|
15
|
+
/**
|
|
16
|
+
* @brief Constructs a new Waveform Length Filter.
|
|
17
|
+
* @param window_size The number of samples to consider in the sliding window.
|
|
18
|
+
*/
|
|
19
|
+
explicit WaveformLengthFilter(size_t window_size)
|
|
20
|
+
: m_filter(window_size), m_previous_sample(0.0f), m_is_initialized(false) {}
|
|
21
|
+
|
|
22
|
+
// Delete copy constructor and copy assignment
|
|
23
|
+
WaveformLengthFilter(const WaveformLengthFilter &) = delete;
|
|
24
|
+
WaveformLengthFilter &operator=(const WaveformLengthFilter &) = delete;
|
|
25
|
+
|
|
26
|
+
// Enable move semantics
|
|
27
|
+
WaveformLengthFilter(WaveformLengthFilter &&) noexcept = default;
|
|
28
|
+
WaveformLengthFilter &operator=(WaveformLengthFilter &&) noexcept = default;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @brief Adds a new sample to the filter.
|
|
32
|
+
* @param newValue The new sample value to add.
|
|
33
|
+
* @return T The updated waveform length over the window.
|
|
34
|
+
*/
|
|
35
|
+
T addSample(float newValue)
|
|
36
|
+
{
|
|
37
|
+
T diff = 0.0;
|
|
38
|
+
if (m_is_initialized)
|
|
39
|
+
{
|
|
40
|
+
diff = std::abs(newValue - m_previous_sample);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
m_previous_sample = newValue;
|
|
44
|
+
m_is_initialized = true;
|
|
45
|
+
|
|
46
|
+
// Add the difference to the window, get the sum of diffs back
|
|
47
|
+
return m_filter.addSample(diff);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @brief Clears all samples from the filter and resets state.
|
|
52
|
+
*/
|
|
53
|
+
void clear()
|
|
54
|
+
{
|
|
55
|
+
m_filter.clear();
|
|
56
|
+
m_previous_sample = 0.0;
|
|
57
|
+
m_is_initialized = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @brief Exports the complete filter state (buffer + previous sample).
|
|
62
|
+
* @return A pair containing the buffer contents, running sum, and previous sample.
|
|
63
|
+
*/
|
|
64
|
+
std::pair<std::pair<std::vector<T>, double>, T> getState() const
|
|
65
|
+
{
|
|
66
|
+
// Returns: { {buffer_of_diffs, running_sum}, previous_sample }
|
|
67
|
+
return std::make_pair(m_filter.getState(), m_previous_sample);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @brief Restores the complete filter state (buffer + previous sample).
|
|
72
|
+
* @param buffer The buffer of differences to restore.
|
|
73
|
+
* @param runningSum The running sum of differences to restore.
|
|
74
|
+
* @param prevSample The previous sample value to restore.
|
|
75
|
+
* @return void
|
|
76
|
+
*/
|
|
77
|
+
void setState(const std::vector<T> &buffer, double runningSum, T prevSample)
|
|
78
|
+
{
|
|
79
|
+
m_filter.setState(buffer, runningSum);
|
|
80
|
+
m_previous_sample = prevSample;
|
|
81
|
+
m_is_initialized = true; // Assume if state is set, we are init'd
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @brief Gets const access to the internal SlidingWindowFilter.
|
|
86
|
+
* @return const SlidingWindowFilter<float, SumPolicy<float>>& Reference to the internal filter
|
|
87
|
+
*/
|
|
88
|
+
const dsp::utils::SlidingWindowFilter<T, SumPolicy<T>> &getInternalFilter() const
|
|
89
|
+
{
|
|
90
|
+
return m_filter;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private:
|
|
94
|
+
dsp::utils::SlidingWindowFilter<T, SumPolicy<T>> m_filter;
|
|
95
|
+
T m_previous_sample;
|
|
96
|
+
bool m_is_initialized;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#include "CircularBufferArray.h"
|
|
2
|
+
#include <algorithm>
|
|
3
|
+
#include <stdexcept>
|
|
4
|
+
#include <memory>
|
|
5
|
+
|
|
6
|
+
using namespace dsp::utils;
|
|
7
|
+
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
// Constructor
|
|
10
|
+
// Initializes the circular buffer with a specified size using std::make_unique
|
|
11
|
+
// @ param size - The size of the circular buffer
|
|
12
|
+
// @ param windowDuration_ms - Optional window duration for time-based expiration (0 = disabled)
|
|
13
|
+
// @ return void
|
|
14
|
+
// -----------------------------------------------------------------------------
|
|
15
|
+
template <typename T>
|
|
16
|
+
CircularBufferArray<T>::CircularBufferArray(size_t size, double windowDuration_ms)
|
|
17
|
+
: buffer(std::make_unique<T[]>(std::max(size, static_cast<size_t>(1)))),
|
|
18
|
+
timestamps(windowDuration_ms > 0.0 ? std::make_unique<double[]>(std::max(size, static_cast<size_t>(1))) : nullptr),
|
|
19
|
+
head(0),
|
|
20
|
+
tail(0),
|
|
21
|
+
capacity(std::max(size, static_cast<size_t>(1))),
|
|
22
|
+
count(0),
|
|
23
|
+
windowDuration_ms(windowDuration_ms)
|
|
24
|
+
{
|
|
25
|
+
// Buffers are automatically initialized by make_unique
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Note: Move constructor and move assignment operator are now defaulted in the header
|
|
29
|
+
// std::unique_ptr handles move semantics correctly by default
|
|
30
|
+
|
|
31
|
+
// -----------------------------------------------------------------------------
|
|
32
|
+
// Method: push
|
|
33
|
+
// Adds an item to the circular buffer
|
|
34
|
+
// @ param item - The item to add
|
|
35
|
+
// @ return bool - True if the item was added, false if the buffer is full
|
|
36
|
+
// -----------------------------------------------------------------------------
|
|
37
|
+
template <typename T>
|
|
38
|
+
bool CircularBufferArray<T>::push(const T &item)
|
|
39
|
+
{
|
|
40
|
+
if (isFull())
|
|
41
|
+
{
|
|
42
|
+
return false; // Buffer is full
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this->buffer[this->head] = item;
|
|
46
|
+
this->head = (this->head + 1) % this->capacity;
|
|
47
|
+
this->count++;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// -----------------------------------------------------------------------------
|
|
52
|
+
// Method: pop
|
|
53
|
+
// Removes an item from the circular buffer
|
|
54
|
+
// @ param item - The item to remove
|
|
55
|
+
// @ return bool - True if the item was removed, false if the buffer is empty
|
|
56
|
+
// -----------------------------------------------------------------------------
|
|
57
|
+
template <typename T>
|
|
58
|
+
bool CircularBufferArray<T>::pop(T &item) noexcept
|
|
59
|
+
{
|
|
60
|
+
if (isEmpty())
|
|
61
|
+
{
|
|
62
|
+
return false; // Buffer is empty
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
item = this->buffer[this->tail];
|
|
66
|
+
this->tail = (this->tail + 1) % this->capacity;
|
|
67
|
+
this->count--;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// -----------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
// Method: clear
|
|
74
|
+
// Clears the circular buffer
|
|
75
|
+
// @ param void
|
|
76
|
+
// @ return void
|
|
77
|
+
// -----------------------------------------------------------------------------
|
|
78
|
+
template <typename T>
|
|
79
|
+
void CircularBufferArray<T>::clear() noexcept
|
|
80
|
+
{
|
|
81
|
+
this->head = 0;
|
|
82
|
+
this->tail = 0;
|
|
83
|
+
this->count = 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// -----------------------------------------------------------------------------
|
|
87
|
+
// Method: pushOverwrite
|
|
88
|
+
// Adds an item to the circular buffer, overwriting the oldest item if full
|
|
89
|
+
// @ param item - The item to add
|
|
90
|
+
// @ return bool - Always returns true
|
|
91
|
+
template <typename T>
|
|
92
|
+
void CircularBufferArray<T>::pushOverwrite(const T &item)
|
|
93
|
+
{
|
|
94
|
+
if (isFull())
|
|
95
|
+
this->tail = (this->tail + 1) % this->capacity;
|
|
96
|
+
this->buffer[this->head] = item;
|
|
97
|
+
this->head = (this->head + 1) % this->capacity;
|
|
98
|
+
if (this->count < this->capacity)
|
|
99
|
+
++this->count;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// -----------------------------------------------------------------------------
|
|
103
|
+
// Getter: getCapacity
|
|
104
|
+
// @ param void
|
|
105
|
+
// @ return size_t - The capacity of the circular buffer
|
|
106
|
+
// -----------------------------------------------------------------------------
|
|
107
|
+
template <typename T>
|
|
108
|
+
size_t CircularBufferArray<T>::getCapacity() const noexcept
|
|
109
|
+
{
|
|
110
|
+
return this->capacity;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// -----------------------------------------------------------------------------
|
|
114
|
+
// Getter: getCount
|
|
115
|
+
// @ param void
|
|
116
|
+
// @ return size_t - The number of elements in the circular buffer
|
|
117
|
+
// -----------------------------------------------------------------------------
|
|
118
|
+
template <typename T>
|
|
119
|
+
size_t CircularBufferArray<T>::getCount() const noexcept
|
|
120
|
+
{
|
|
121
|
+
return this->count;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// -----------------------------------------------------------------------------
|
|
125
|
+
// Getter: isEmpty
|
|
126
|
+
// @ param void
|
|
127
|
+
// @ return bool - True if the buffer is empty, false otherwise
|
|
128
|
+
// -----------------------------------------------------------------------------
|
|
129
|
+
template <typename T>
|
|
130
|
+
bool CircularBufferArray<T>::isEmpty() const noexcept
|
|
131
|
+
{
|
|
132
|
+
return this->count == 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// -----------------------------------------------------------------------------
|
|
136
|
+
// Getter: isFull
|
|
137
|
+
// @ param void
|
|
138
|
+
// @ return bool - True if the buffer is full, false otherwise
|
|
139
|
+
// -----------------------------------------------------------------------------
|
|
140
|
+
template <typename T>
|
|
141
|
+
bool CircularBufferArray<T>::isFull() const noexcept
|
|
142
|
+
{
|
|
143
|
+
return this->count == this->capacity;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// -----------------------------------------------------------------------------
|
|
147
|
+
// Method: peek
|
|
148
|
+
// Returns the item at the head of the circular buffer without removing it
|
|
149
|
+
// @ param void
|
|
150
|
+
// @ return T - The item at the head of the buffer
|
|
151
|
+
template <typename T>
|
|
152
|
+
T CircularBufferArray<T>::peek() const
|
|
153
|
+
{
|
|
154
|
+
if (isEmpty())
|
|
155
|
+
{
|
|
156
|
+
throw std::runtime_error("Buffer is empty");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return this->buffer[this->tail];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// -----------------------------------------------------------------------------
|
|
163
|
+
// Method: toVector
|
|
164
|
+
// Exports the buffer contents in order (oldest to newest) as a vector
|
|
165
|
+
// @ return std::vector<T> - The buffer contents in order
|
|
166
|
+
// -----------------------------------------------------------------------------
|
|
167
|
+
template <typename T>
|
|
168
|
+
std::vector<T> CircularBufferArray<T>::toVector() const
|
|
169
|
+
{
|
|
170
|
+
std::vector<T> result;
|
|
171
|
+
result.reserve(this->count);
|
|
172
|
+
|
|
173
|
+
for (size_t i = 0; i < this->count; ++i)
|
|
174
|
+
{
|
|
175
|
+
size_t index = (this->tail + i) % this->capacity;
|
|
176
|
+
result.push_back(this->buffer[index]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// -----------------------------------------------------------------------------
|
|
183
|
+
// Method: fromVector
|
|
184
|
+
// Imports buffer contents from a vector, maintaining order
|
|
185
|
+
// @ param data - The vector containing the data to import
|
|
186
|
+
// -----------------------------------------------------------------------------
|
|
187
|
+
template <typename T>
|
|
188
|
+
void CircularBufferArray<T>::fromVector(const std::vector<T> &data)
|
|
189
|
+
{
|
|
190
|
+
clear();
|
|
191
|
+
|
|
192
|
+
for (const auto &item : data)
|
|
193
|
+
{
|
|
194
|
+
pushOverwrite(item);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// -----------------------------------------------------------------------------
|
|
199
|
+
// Method: pushWithTimestamp
|
|
200
|
+
// Adds an item with a timestamp to the buffer (requires time-aware mode)
|
|
201
|
+
// @ param item - The item to add
|
|
202
|
+
// @ param timestamp - The timestamp in milliseconds
|
|
203
|
+
// -----------------------------------------------------------------------------
|
|
204
|
+
template <typename T>
|
|
205
|
+
void CircularBufferArray<T>::pushWithTimestamp(const T &item, double timestamp)
|
|
206
|
+
{
|
|
207
|
+
if (!isTimeAware())
|
|
208
|
+
{
|
|
209
|
+
throw std::runtime_error("pushWithTimestamp requires time-aware mode (windowDuration > 0)");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (isFull())
|
|
213
|
+
{
|
|
214
|
+
return; // Buffer is full
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
buffer[head] = item;
|
|
218
|
+
timestamps[head] = timestamp;
|
|
219
|
+
head = (head + 1) % capacity;
|
|
220
|
+
count++;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// -----------------------------------------------------------------------------
|
|
224
|
+
// Method: pushOverwriteWithTimestamp
|
|
225
|
+
// Adds an item with timestamp, overwriting oldest if full (requires time-aware mode)
|
|
226
|
+
// @ param item - The item to add
|
|
227
|
+
// @ param timestamp - The timestamp in milliseconds
|
|
228
|
+
// -----------------------------------------------------------------------------
|
|
229
|
+
template <typename T>
|
|
230
|
+
void CircularBufferArray<T>::pushOverwriteWithTimestamp(const T &item, double timestamp)
|
|
231
|
+
{
|
|
232
|
+
if (!isTimeAware())
|
|
233
|
+
{
|
|
234
|
+
throw std::runtime_error("pushOverwriteWithTimestamp requires time-aware mode (windowDuration > 0)");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (isFull())
|
|
238
|
+
{
|
|
239
|
+
tail = (tail + 1) % capacity;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
buffer[head] = item;
|
|
243
|
+
timestamps[head] = timestamp;
|
|
244
|
+
head = (head + 1) % capacity;
|
|
245
|
+
|
|
246
|
+
if (count < capacity)
|
|
247
|
+
{
|
|
248
|
+
++count;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// -----------------------------------------------------------------------------
|
|
253
|
+
// Method: expireOld
|
|
254
|
+
// Removes samples older than windowDuration from the current timestamp
|
|
255
|
+
// @ param currentTimestamp - The current timestamp in milliseconds
|
|
256
|
+
// @ return size_t - Number of samples expired
|
|
257
|
+
// -----------------------------------------------------------------------------
|
|
258
|
+
template <typename T>
|
|
259
|
+
size_t CircularBufferArray<T>::expireOld(double currentTimestamp)
|
|
260
|
+
{
|
|
261
|
+
if (!isTimeAware() || isEmpty())
|
|
262
|
+
{
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
size_t expired_count = 0;
|
|
267
|
+
double cutoff_time = currentTimestamp - windowDuration_ms;
|
|
268
|
+
|
|
269
|
+
// Remove samples from tail while they're older than cutoff
|
|
270
|
+
while (count > 0 && timestamps[tail] < cutoff_time)
|
|
271
|
+
{
|
|
272
|
+
tail = (tail + 1) % capacity;
|
|
273
|
+
--count;
|
|
274
|
+
++expired_count;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return expired_count;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// -----------------------------------------------------------------------------
|
|
281
|
+
// Method: toVectorWithTimestamps
|
|
282
|
+
// Exports the buffer contents with timestamps (requires time-aware mode)
|
|
283
|
+
// @ return std::vector<std::pair<double, T>> - The buffer contents with timestamps
|
|
284
|
+
// -----------------------------------------------------------------------------
|
|
285
|
+
template <typename T>
|
|
286
|
+
std::vector<std::pair<double, T>> CircularBufferArray<T>::toVectorWithTimestamps() const
|
|
287
|
+
{
|
|
288
|
+
if (!isTimeAware())
|
|
289
|
+
{
|
|
290
|
+
throw std::runtime_error("toVectorWithTimestamps requires time-aware mode");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
std::vector<std::pair<double, T>> result;
|
|
294
|
+
result.reserve(count);
|
|
295
|
+
|
|
296
|
+
for (size_t i = 0; i < count; ++i)
|
|
297
|
+
{
|
|
298
|
+
size_t index = (tail + i) % capacity;
|
|
299
|
+
result.push_back({timestamps[index], buffer[index]});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// -----------------------------------------------------------------------------
|
|
306
|
+
// Method: fromVectorWithTimestamps
|
|
307
|
+
// Imports buffer contents with timestamps (requires time-aware mode)
|
|
308
|
+
// @ param data - The vector containing (timestamp, value) pairs
|
|
309
|
+
// -----------------------------------------------------------------------------
|
|
310
|
+
template <typename T>
|
|
311
|
+
void CircularBufferArray<T>::fromVectorWithTimestamps(const std::vector<std::pair<double, T>> &data)
|
|
312
|
+
{
|
|
313
|
+
if (!isTimeAware())
|
|
314
|
+
{
|
|
315
|
+
throw std::runtime_error("fromVectorWithTimestamps requires time-aware mode");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
clear();
|
|
319
|
+
|
|
320
|
+
for (const auto &[timestamp, value] : data)
|
|
321
|
+
{
|
|
322
|
+
pushOverwriteWithTimestamp(value, timestamp);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Note: Destructor is now defaulted in the header
|
|
327
|
+
// std::unique_ptr automatically cleans up the buffers
|
|
328
|
+
|
|
329
|
+
// Explicit template instantiation for common types
|
|
330
|
+
namespace dsp::utils
|
|
331
|
+
{
|
|
332
|
+
template class CircularBufferArray<int>;
|
|
333
|
+
template class CircularBufferArray<float>;
|
|
334
|
+
template class CircularBufferArray<double>;
|
|
335
|
+
template class CircularBufferArray<bool>;
|
|
336
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <vector>
|
|
4
|
+
#include <stdexcept>
|
|
5
|
+
#include <memory>
|
|
6
|
+
|
|
7
|
+
namespace dsp::utils
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
template <typename T>
|
|
11
|
+
|
|
12
|
+
class CircularBufferArray
|
|
13
|
+
{
|
|
14
|
+
public:
|
|
15
|
+
// constructors
|
|
16
|
+
explicit CircularBufferArray(size_t size, double windowDuration_ms = 0.0);
|
|
17
|
+
CircularBufferArray(const CircularBufferArray &other) = delete; // disable copy to avoid shallow copy
|
|
18
|
+
CircularBufferArray &operator=(const CircularBufferArray &other) = delete; // disable copy assignment to avoid shallow copy
|
|
19
|
+
|
|
20
|
+
// move semantics (defaulted - compiler generated is now safe with unique_ptr)
|
|
21
|
+
CircularBufferArray(CircularBufferArray &&other) noexcept = default;
|
|
22
|
+
CircularBufferArray &operator=(CircularBufferArray &&other) noexcept = default;
|
|
23
|
+
|
|
24
|
+
// methods
|
|
25
|
+
bool push(const T &item);
|
|
26
|
+
bool pop(T &item) noexcept;
|
|
27
|
+
void clear() noexcept;
|
|
28
|
+
void pushOverwrite(const T &item);
|
|
29
|
+
|
|
30
|
+
// Time-aware methods (require timestamps to be enabled)
|
|
31
|
+
void pushWithTimestamp(const T &item, double timestamp);
|
|
32
|
+
void pushOverwriteWithTimestamp(const T &item, double timestamp);
|
|
33
|
+
size_t expireOld(double currentTimestamp);
|
|
34
|
+
|
|
35
|
+
// getters
|
|
36
|
+
size_t getCapacity() const noexcept;
|
|
37
|
+
size_t getCount() const noexcept;
|
|
38
|
+
bool isEmpty() const noexcept;
|
|
39
|
+
bool isFull() const noexcept;
|
|
40
|
+
T peek() const;
|
|
41
|
+
bool isTimeAware() const noexcept { return windowDuration_ms > 0.0; }
|
|
42
|
+
double getWindowDuration() const noexcept { return windowDuration_ms; }
|
|
43
|
+
|
|
44
|
+
// state management
|
|
45
|
+
std::vector<T> toVector() const;
|
|
46
|
+
void fromVector(const std::vector<T> &data);
|
|
47
|
+
std::vector<std::pair<double, T>> toVectorWithTimestamps() const;
|
|
48
|
+
void fromVectorWithTimestamps(const std::vector<std::pair<double, T>> &data);
|
|
49
|
+
|
|
50
|
+
// destructor (defaulted - unique_ptr handles cleanup automatically)
|
|
51
|
+
~CircularBufferArray() = default;
|
|
52
|
+
|
|
53
|
+
private:
|
|
54
|
+
std::unique_ptr<T[]> buffer;
|
|
55
|
+
std::unique_ptr<double[]> timestamps; // Optional timestamp array (nullptr if not time-aware)
|
|
56
|
+
size_t head;
|
|
57
|
+
size_t tail;
|
|
58
|
+
size_t capacity;
|
|
59
|
+
size_t count;
|
|
60
|
+
double windowDuration_ms; // Maximum age of samples (0 = disabled)
|
|
61
|
+
};
|
|
62
|
+
} // namespace dsp::utils
|