-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathwindows.h
219 lines (196 loc) · 8.01 KB
/
windows.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#include "./common.h"
#ifndef SIGNALSMITH_DSP_WINDOWS_H
#define SIGNALSMITH_DSP_WINDOWS_H
#include <cmath>
#include <algorithm>
namespace signalsmith {
namespace windows {
/** @defgroup Windows Window functions
@brief Windows for spectral analysis
These are generally double-precision, because they are mostly calculated during setup/reconfiguring, not real-time code.
@{
@file
*/
/** @brief The Kaiser window (almost) maximises the energy in the main-lobe compared to the side-lobes.
Kaiser windows can be constructing using the shape-parameter (beta) or using the static `with???()` methods.*/
class Kaiser {
// I_0(x)=\sum_{k=0}^{N}\frac{x^{2k}}{(k!)^2\cdot4^k}
inline static double bessel0(double x) {
const double significanceLimit = 1e-4;
double result = 0;
double term = 1;
double m = 0;
while (term > significanceLimit) {
result += term;
++m;
term *= (x*x)/(4*m*m);
}
return result;
}
double beta;
double invB0;
static double heuristicBandwidth(double bandwidth) {
// Good peaks
//return bandwidth + 8/((bandwidth + 3)*(bandwidth + 3));
// Good average
//return bandwidth + 14/((bandwidth + 2.5)*(bandwidth + 2.5));
// Compromise
return bandwidth + 8/((bandwidth + 3)*(bandwidth + 3)) + 0.25*std::max(3 - bandwidth, 0.0);
}
public:
/// Set up a Kaiser window with a given shape. `beta` is `pi*alpha` (since there is ambiguity about shape parameters)
Kaiser(double beta) : beta(beta), invB0(1/bessel0(beta)) {}
/// @name Bandwidth methods
/// @{
static Kaiser withBandwidth(double bandwidth, bool heuristicOptimal=false) {
return Kaiser(bandwidthToBeta(bandwidth, heuristicOptimal));
}
/** Returns the Kaiser shape where the main lobe has the specified bandwidth (as a factor of 1/window-length).
\diagram{kaiser-windows.svg,You can see that the main lobe matches the specified bandwidth.}
If `heuristicOptimal` is enabled, the main lobe width is _slightly_ wider, improving both the peak and total energy - see `bandwidthToEnergyDb()` and `bandwidthToPeakDb()`.
\diagram{kaiser-windows-heuristic.svg, The main lobe extends to ±bandwidth/2.} */
static double bandwidthToBeta(double bandwidth, bool heuristicOptimal=false) {
if (heuristicOptimal) { // Heuristic based on numerical search
bandwidth = heuristicBandwidth(bandwidth);
}
bandwidth = std::max(bandwidth, 2.0);
double alpha = std::sqrt(bandwidth*bandwidth*0.25 - 1);
return alpha*M_PI;
}
static double betaToBandwidth(double beta) {
double alpha = beta*(1.0/M_PI);
return 2*std::sqrt(alpha*alpha + 1);
}
/// @}
/// @name Performance methods
/// @{
/** @brief Total energy ratio (in dB) between side-lobes and the main lobe.
\diagram{windows-kaiser-sidelobe-energy.svg,Measured main/side lobe energy ratio. You can see that the heuristic improves performance for all bandwidth values.}
This function uses an approximation which is accurate to ±0.5dB for 2 ⩽ bandwidth ≤ 10, or 1 ⩽ bandwidth ≤ 10 when `heuristicOptimal`is enabled.
*/
static double bandwidthToEnergyDb(double bandwidth, bool heuristicOptimal=false) {
// Horrible heuristic fits
if (heuristicOptimal) {
if (bandwidth < 3) bandwidth += (3 - bandwidth)*0.5;
return 12.9 + -3/(bandwidth + 0.4) - 13.4*bandwidth + (bandwidth < 3)*-9.6*(bandwidth - 3);
}
return 10.5 + 15/(bandwidth + 0.4) - 13.25*bandwidth + (bandwidth < 2)*13*(bandwidth - 2);
}
static double energyDbToBandwidth(double energyDb, bool heuristicOptimal=false) {
double bw = 1;
while (bw < 20 && bandwidthToEnergyDb(bw, heuristicOptimal) > energyDb) {
bw *= 2;
}
double step = bw/2;
while (step > 0.0001) {
if (bandwidthToEnergyDb(bw, heuristicOptimal) > energyDb) {
bw += step;
} else {
bw -= step;
}
step *= 0.5;
}
return bw;
}
/** @brief Peak ratio (in dB) between side-lobes and the main lobe.
\diagram{windows-kaiser-sidelobe-peaks.svg,Measured main/side lobe peak ratio. You can see that the heuristic improves performance, except in the bandwidth range 1-2 where peak ratio was sacrificed to improve total energy ratio.}
This function uses an approximation which is accurate to ±0.5dB for 2 ⩽ bandwidth ≤ 9, or 0.5 ⩽ bandwidth ≤ 9 when `heuristicOptimal`is enabled.
*/
static double bandwidthToPeakDb(double bandwidth, bool heuristicOptimal=false) {
// Horrible heuristic fits
if (heuristicOptimal) {
return 14.2 - 20/(bandwidth + 1) - 13*bandwidth + (bandwidth < 3)*-6*(bandwidth - 3) + (bandwidth < 2.25)*5.8*(bandwidth - 2.25);
}
return 10 + 8/(bandwidth + 2) - 12.75*bandwidth + (bandwidth < 2)*4*(bandwidth - 2);
}
static double peakDbToBandwidth(double peakDb, bool heuristicOptimal=false) {
double bw = 1;
while (bw < 20 && bandwidthToPeakDb(bw, heuristicOptimal) > peakDb) {
bw *= 2;
}
double step = bw/2;
while (step > 0.0001) {
if (bandwidthToPeakDb(bw, heuristicOptimal) > peakDb) {
bw += step;
} else {
bw -= step;
}
step *= 0.5;
}
return bw;
}
/** @} */
/** Equivalent noise bandwidth (ENBW), a measure of frequency resolution.
\diagram{windows-kaiser-enbw.svg,Measured ENBW\, with and without the heuristic bandwidth adjustment.}
This approximation is accurate to ±0.05 up to a bandwidth of 22.
*/
static double bandwidthToEnbw(double bandwidth, bool heuristicOptimal=false) {
if (heuristicOptimal) bandwidth = heuristicBandwidth(bandwidth);
double b2 = std::max<double>(bandwidth - 2, 0);
return 1 + b2*(0.2 + b2*(-0.005 + b2*(-0.000005 + b2*0.0000022)));
}
/// Return the window's value for position in the range [0, 1]
double operator ()(double unit) {
double r = 2*unit - 1;
double arg = std::sqrt(1 - r*r);
return bessel0(beta*arg)*invB0;
}
/// Fills an arbitrary container with a Kaiser window
template<typename Data>
void fill(Data &&data, int size) const {
double invSize = 1.0/size;
for (int i = 0; i < size; ++i) {
double r = (2*i + 1)*invSize - 1;
double arg = std::sqrt(1 - r*r);
data[i] = bessel0(beta*arg)*invB0;
}
}
};
/** @brief The Approximate Confined Gaussian window is (almost) optimal
ACG windows can be constructing using the shape-parameter (sigma) or using the static `with???()` methods.*/
class ApproximateConfinedGaussian {
double gaussianFactor;
double gaussian(double x) const {
return std::exp(-x*x*gaussianFactor);
}
public:
/// Heuristic map from bandwidth to the appropriately-optimal sigma
static double bandwidthToSigma(double bandwidth) {
return 0.3/std::sqrt(bandwidth);
}
static ApproximateConfinedGaussian withBandwidth(double bandwidth) {
return ApproximateConfinedGaussian(bandwidthToSigma(bandwidth));
}
ApproximateConfinedGaussian(double sigma) : gaussianFactor(0.0625/(sigma*sigma)) {}
/// Fills an arbitrary container
template<typename Data>
void fill(Data &&data, int size) const {
double invSize = 1.0/size;
double offsetScale = gaussian(1)/(gaussian(3) + gaussian(-1));
double norm = 1/(gaussian(0) - 2*offsetScale*(gaussian(2)));
for (int i = 0; i < size; ++i) {
double r = (2*i + 1)*invSize - 1;
data[i] = norm*(gaussian(r) - offsetScale*(gaussian(r - 2) + gaussian(r + 2)));
}
}
};
/** Forces STFT perfect-reconstruction (WOLA) on an existing window, for a given STFT interval.
For example, here are perfect-reconstruction versions of the approximately-optimal @ref Kaiser windows:
\diagram{kaiser-windows-heuristic-pr.svg,Note the lower overall energy\, and the pointy top for 2x bandwidth. Spectral performance is about the same\, though.}
*/
template<typename Data>
void forcePerfectReconstruction(Data &&data, int windowLength, int interval) {
for (int i = 0; i < interval; ++i) {
double sum2 = 0;
for (int index = i; index < windowLength; index += interval) {
sum2 += data[index]*data[index];
}
double factor = 1/std::sqrt(sum2);
for (int index = i; index < windowLength; index += interval) {
data[index] *= factor;
}
}
}
/** @} */
}} // signalsmith::windows
#endif // include guard