Roc Toolkit internal modules
Roc Toolkit: real-time audio streaming
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 
20 namespace roc {
21 namespace 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.
29 template <typename T> class MovStats {
30 public:
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.
138  ROC_ATTR_NODISCARD bool extend_win(const size_t new_win) {
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 
159 private:
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
ROC_ATTR_NODISCARD bool extend_win(const size_t new_win)
Extend rolling window length.
Definition: mov_stats.h:138
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.