Roc Toolkit internal modules
Roc Toolkit: real-time audio streaming
Toggle main menu visibility
Loading...
Searching...
No Matches
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_
array.h
Dynamic array.
ROC_ATTR_NODISCARD
#define ROC_ATTR_NODISCARD
Emit warning if function result is not checked.
Definition
attributes.h:31
roc::core::IArena
Memory arena interface.
Definition
iarena.h:23
roc::core::MovStats::mov_max
T mov_max() const
Max value in sliding window.
Definition
mov_stats.h:119
roc::core::MovStats::mov_min
T mov_min() const
Min value in sliding window.
Definition
mov_stats.h:124
roc::core::MovStats::is_valid
bool is_valid() const
Check that initial allocation succeeded.
Definition
mov_stats.h:67
roc::core::MovStats::mov_avg
T mov_avg() const
Get moving average value.
Definition
mov_stats.h:93
roc::core::MovStats::add
void add(const T &x)
Shift rolling window by one sample x.
Definition
mov_stats.h:72
roc::core::MovStats::MovStats
MovStats(IArena &arena, const size_t win_len)
Initialize.
Definition
mov_stats.h:32
roc::core::MovStats::mov_var
T mov_var() const
Get variance.
Definition
mov_stats.h:106
roc::core::MovStats::extend_win
ROC_ATTR_NODISCARD bool extend_win(const size_t new_win)
Extend rolling window length.
Definition
mov_stats.h:138
iarena.h
Memory arena interface.
roc::core
General-purpose building blocks and platform abstraction layer.
roc
Root namespace.
panic.h
Panic.
roc_panic
#define roc_panic(...)
Print error message and terminate program gracefully.
Definition
panic.h:50
ring_queue.h
Queue on continuous memory buffer.
roc_core
mov_stats.h
Generated by
1.17.0