Analysis/Synthesis window of STFT

这里提一个(i)STFT的细节。做短时傅里叶分析的时候,通常会选取窗函数,缓解频谱泄露。把频域的谱通过OLA(overlap add)转换到时域上时,我以前的做法是选取相同的窗函数。这么做会带来一个问题,如果直接把OLA的结果写入磁盘,特别容易出现clipping,所以我通常会在OLA之后对samples做一个renorm/rescale操作,避免数值超出wav的采样单元,但是这么一做,往往就丢失了原先音频的能量信息。理想情况下,在频域不做任何处理,变换到时域,应该存在条件,使得时域结果完美重构的。

后来逐渐的在一些文献中看到了一些资料。发现这个窗函数的选取,不是那么随意,要想达到最佳重构,正逆过程的窗需要满足一个约束条件,有些文献中也称为双正交。正过程的窗称为分析(analysis)窗,逆过程的窗(synthesis)称为合成窗。下面导出这个约束条件。

令$t$表示帧索引,$S$表示帧移,$w, v$分别表示analysis和synthesis window。$s, \hat{s}$表示原始信号和合成信号。

基于以上定义,分帧过程可以表示为:

OLA过程表示为:

将式$(1)$重写为:

带入$(2)$式

因此达到完美重构条件(完全相等),analysis和synthesis窗需要满足条件

此式即为所谓的双正交约束。

另外在做短时傅里叶分析的时候,主流工具喜欢对wave进行padding,以期望达到更好的重构效果,这里这么理解,如果不进行padding的话,第一帧和最后一帧必然存在某些samples,无法通过叠加得到(即仅仅只做了加窗),因此,这些点难以达到完美重构条件,padding的目的是对这些点位置进行shift,尽可能的使它们可能达到完美重构条件(这里理解未必正确,看官需小心)。

其次,librosa和fgnt的实现版本也不完全一样,前者是对合成的samples最后做了normalize,后者则是根据analysis窗,构建synthesis窗,之后进行OLA,我参考后者,在kaldi-enhan上的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void ShortTimeFTComputer::CacheSynthesisWindow(const ShortTimeFTOptions &opts) {
int32 window_size = opts_.frame_length;

Vector<BaseFloat> analysis_window_square(analysis_window_), denominator(window_size);
analysis_window_square.ApplyPow(2);

int32 width = static_cast<int32>((window_size - 1) / opts_.frame_shift), s;

for (int32 i = -width; i <= width; i++) {
s = i * opts_.frame_shift;
if (s < 0) {
// [0: end - s] += [-s: end]
denominator.Range(0, window_size + s).AddVec(1, analysis_window_square.Range(-s, window_size + s));
} else{
// [s: end] += [0: end - s]
denominator.Range(s, window_size - s).AddVec(1, analysis_window_square.Range(0, window_size - s));
}
}
synthesis_window_.DivElements(denominator);
}

关于这里提的问题,在”Springer Handbook of Speech Processing”的12.1节 The Short-Time Fourier Transform中有详细论述,想要继续深入的可以做一下参考。