-
Notifications
You must be signed in to change notification settings - Fork 14
/
windows.h
164 lines (148 loc) · 5.97 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
#ifndef SIGNALSMITH_DSP_WINDOWS_H
#define SIGNALSMITH_DSP_WINDOWS_H
#include "./common.h"
#include <cmath>
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 {
inline static double bessel0(double x) {
constexpr double significanceLimit = 1e-6;
double result = 0;
double term = 1;
double m = 0;
while (term > result*significanceLimit) {
result += term;
++m;
term *= (x*x) / (4*m*m);
}
return result;
}
double beta;
double invB0;
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
// Good peaks
//bandwidth += 8/((bandwidth + 3)*(bandwidth + 3));
// Good average
//bandwidth += 14/((bandwidth + 2.5)*(bandwidth + 2.5));
// Compromise
bandwidth += 8/((bandwidth + 3)*(bandwidth + 3)) + 0.25*std::max(3 - bandwidth, 0.0);
}
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{kaiser-bandwidth-sidelobes-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{kaiser-bandwidth-sidelobes-peak.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;
}
/** @} */
/// 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;
}
}
};
/** 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