Roc Toolkit internal modules
Roc Toolkit: real-time audio streaming
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
mov_stats.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2023 Roc Streaming authors
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 */
8
9//! @file roc_core/mov_stats.h
10//! @brief Rolling window moving average and variance.
11
12#ifndef ROC_CORE_MOV_STATS_H_
13#define ROC_CORE_MOV_STATS_H_
14
15#include "roc_core/array.h"
16#include "roc_core/iarena.h"
17#include "roc_core/panic.h"
18#include "roc_core/ring_queue.h"
19
20namespace roc {
21namespace core {
22
23//! Rolling window moving average and variance.
24//! @remarks
25//! Efficiently implements moving average and variance based on approach
26//! described in https://www.dsprelated.com/showthread/comp.dsp/97276-1.php
27//!
28//! @tparam T defines a sample type.
29template <typename T> class MovStats {
30public:
31 //! Initialize.
32 MovStats(IArena& arena, const size_t win_len)
33 : buffer_(arena)
34 , buffer2_(arena)
35 , win_len_(win_len)
36 , buffer_i_(0)
37 , movsum_(T(0))
38 , movsum2_(T(0))
39 , mov_var_(T(0))
40 , mov_max_cntr_(0)
41 , full_(false)
42 , first_(true)
43 , queue_max_(arena, win_len + 1)
44 , curr_max_(T(0))
45 , queue_min_(arena, win_len + 1)
46 , curr_min_(T(0))
47 , valid_(false) {
48 if (win_len == 0) {
49 roc_panic("mov stats: window length must be greater than 0");
50 }
51
52 if (!queue_max_.is_valid() || !queue_min_.is_valid()) {
53 return;
54 }
55
56 if (!buffer_.resize(win_len)) {
57 return;
58 }
59 if (!buffer2_.resize(win_len)) {
60 return;
61 }
62
63 valid_ = true;
64 }
65
66 //! Check that initial allocation succeeded.
67 bool is_valid() const {
68 return valid_;
69 }
70
71 //! Shift rolling window by one sample x.
72 void add(const T& x) {
73 const T x2 = x * x;
74 const T x_old = buffer_[buffer_i_];
75 buffer_[buffer_i_] = x;
76 const T x2_old = buffer2_[buffer_i_];
77 buffer2_[buffer_i_] = x2;
78
79 movsum_ += x - x_old;
80 movsum2_ += x2 - x2_old;
81
82 buffer_i_++;
83 if (buffer_i_ == win_len_) {
84 buffer_i_ = 0;
85 full_ = true;
86 }
87
88 slide_max_(x, x_old);
89 slide_min_(x, x_old);
90 }
91
92 //! Get moving average value.
93 T mov_avg() const {
94 T n = 0;
95 if (full_) {
96 n = T(win_len_);
97 } else if (buffer_i_ == 0) {
98 return T(0);
99 } else {
100 n = T(buffer_i_);
101 }
102 return movsum_ / n;
103 }
104
105 //! Get variance.
106 T mov_var() const {
107 T n = 0;
108 if (full_) {
109 n = T(win_len_);
110 } else if (buffer_i_ == 0) {
111 return T(0);
112 } else {
113 n = T(buffer_i_);
114 }
115 return (T)sqrt((n * movsum2_ - movsum_ * movsum_) / (n * n));
116 }
117
118 //! Max value in sliding window.
119 T mov_max() const {
120 return curr_max_;
121 }
122
123 //! Min value in sliding window.
124 T mov_min() const {
125 return curr_min_;
126 }
127
128 //! Extend rolling window length.
129 //! @remarks
130 //! Potentially could cause a gap in the estimated values as
131 //! decreases effective window size by dropping samples to the right from
132 //! the cursor in the ring buffers:
133 //! buffer_i_ win_len_ old win_len_ new
134 //! ↓ ↓ ↓
135 //! [■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□--------------------]
136 //! ↑ ↑ ↑
137 //! Dropped samples.
139 if (new_win <= win_len_) {
140 roc_panic("mov stats: the window length can only grow");
141 }
142 if (!buffer_.resize(new_win)) {
143 return false;
144 }
145 if (!buffer2_.resize(new_win)) {
146 return false;
147 }
148
149 movsum_ = 0;
150 movsum2_ = 0;
151 for (size_t i = 0; i < buffer_i_; i++) {
152 movsum_ += buffer_[i];
153 movsum2_ += buffer2_[i];
154 }
155 full_ = false;
156 return true;
157 }
158
159private:
160 //! Keeping a sliding max by using a sorted deque.
161 //! @remarks
162 //! The wedge is always sorted in descending order.
163 //! The current max is always at the front of the wedge.
164 //! https://www.geeksforgeeks.org/sliding-window-maximum-maximum-of-all-subarrays-of-size-k/
165 void slide_max_(const T& x, const T x_old) {
166 if (queue_max_.is_empty()) {
167 queue_max_.push_back(x);
168 curr_max_ = x;
169 } else {
170 if (queue_max_.front() == x_old) {
171 queue_max_.pop_front();
172 curr_max_ = queue_max_.is_empty() ? x : queue_max_.front();
173 }
174 while (!queue_max_.is_empty() && queue_max_.back() < x) {
175 queue_max_.pop_back();
176 }
177 if (queue_max_.is_empty()) {
178 curr_max_ = x;
179 }
180 queue_max_.push_back(x);
181 }
182 }
183
184 //! Keeping a sliding min by using a sorted deque.
185 //! @remarks
186 //! The wedge is always sorted in ascending order.
187 //! The current min is always at the front of the wedge.
188 //! https://www.geeksforgeeks.org/sliding-window-maximum-maximum-of-all-subarrays-of-size-k/
189 void slide_min_(const T& x, const T x_old) {
190 if (queue_min_.is_empty()) {
191 queue_min_.push_back(x);
192 curr_min_ = x;
193 } else {
194 if (queue_min_.front() == x_old) {
195 queue_min_.pop_front();
196 curr_min_ = queue_min_.is_empty() ? x : queue_min_.front();
197 }
198 while (!queue_min_.is_empty() && queue_min_.back() > x) {
199 queue_min_.pop_back();
200 }
201 if (queue_min_.is_empty()) {
202 curr_min_ = x;
203 }
204 queue_min_.push_back(x);
205 }
206 }
207
208 Array<T> buffer_;
209 Array<T> buffer2_;
210
211 const size_t win_len_;
212 size_t buffer_i_;
213 T movsum_;
214 T movsum2_;
215 T mov_var_;
216 T mov_max_;
217 size_t mov_max_cntr_;
218
219 bool full_;
220 bool first_;
221
222 RingQueue<T> queue_max_;
223 T curr_max_;
224 RingQueue<T> queue_min_;
225 T curr_min_;
226
227 bool valid_;
228};
229
230} // namespace core
231} // namespace roc
232
233#endif // ROC_CORE_MOV_STATS_H_
Dynamic array.
#define ROC_ATTR_NODISCARD
Emit warning if function result is not checked.
Definition attributes.h:31
Memory arena interface.
Definition iarena.h:23
Rolling window moving average and variance.
Definition mov_stats.h:29
T mov_max() const
Max value in sliding window.
Definition mov_stats.h:119
T mov_min() const
Min value in sliding window.
Definition mov_stats.h:124
bool is_valid() const
Check that initial allocation succeeded.
Definition mov_stats.h:67
T mov_avg() const
Get moving average value.
Definition mov_stats.h:93
void add(const T &x)
Shift rolling window by one sample x.
Definition mov_stats.h:72
MovStats(IArena &arena, const size_t win_len)
Initialize.
Definition mov_stats.h:32
T mov_var() const
Get variance.
Definition mov_stats.h:106
bool extend_win(const size_t new_win)
Extend rolling window length.
Definition mov_stats.h:138
Shared ownership intrusive pointer.
Definition shared_ptr.h:32
Memory arena interface.
Root namespace.
Panic.
#define roc_panic(...)
Print error message and terminate program gracefully.
Definition panic.h:50
Queue on continuous memory buffer.