我在 Apple Developer 的文档示例之上构建,称为 Computing the Mel Spectrum Using Linear Algebra。我的目标是扩展此示例,以便能够将其应用于从现场麦克风录制的样本。具体来说,我通过以下方式使用此示例中的子例程:
while(i*hopCnt + windowSize < samples.count) {
if i*hopCnt + windowSize > samples.count {
samplesInThisWindow = Array(samples[i*hopCnt+windowSize..<samples.count])
samplesInThisWindow.append(
contentsOf: [Float].init(repeating: 0, count: i*hopCnt+windowSize - samples.count)
)
} else {
samplesInThisWindow = Array(samples[i*hopCnt..<i*hopCnt+windowSize])
}
let FFTValues = subroutineFromExample(samplesInThisWindow: &samplesInThisWindow)
stftSpectrogram.append(contentOf: FFTValues)
}
return stftSpectrogram
完成此例程后,我返回样本的 STFT 变换和 Mel 频谱图尚未计算。这意味着 FFT 是一个 (time_bins x window_size) 矩阵,其中 time_bins = (samples_count - window_size)/hop_size + 1.
此时我回到使用示例中的代码计算 Mel 频谱图,它生成一个 MEL
filterBanks
矩阵,它是一个 (filterbanks_count x window_size) 矩阵(不确定,我猜是因为 makeFilterBank
方法包含以下代码:row = i * window_size
)。然后代码继续执行以下代码,通过矩阵乘法计算频谱图:
cblas_sgemm(CblasRowMajor,
CblasTrans,
CblasTrans,
Int32(1),
Int32(self.filterbanksCount),
Int32(self.windowSize),
1,
fftResultPtr.baseAddress,
Int32(1),
filterBank.baseAddress,
Int32(self.windowSize),
0,
sgemmResult!.baseAddress,
Int32(self.filterbanksCount)
)
所以,根据文档,这要么计算 C←αAB + βC 要么 C←αBA + βC 其中 A 和 B 可以选择转置。
示例中的代码期望收到一个 (1 x window_size) 矩阵作为 FFT 的结果,因为它一次处理一个时间 bin,因此在这种情况下,FFT 是 (1 x window_size),MEL
filterBanks
是 (filterbanks_count x 窗口大小)。由于 CblasTrans
是为两个输入矩阵指定的,并且 A^tB^t 和 B^tA^t 都不是产品的正确尺寸,因此我假设 MEL filterBanks
实际上是 (window_size x filterbanks_count) ,这意味着sgemmResult = MEL^t*FFT^t
和cblas_sgemm
在C←αBA + βC模式下运行。
这意味着 sgemmResult 是一个 (filterbanks_count x 1) 矩阵。
现在归纳起来有点麻烦,因为我的界面希望接收旋转频谱图作为输入,即 (time_bins x window_size) 矩阵,而明显的归纳将替换所有硬编码
1
s 在代码中带有 FFT.count/window_size
(FFT 以 row-major order 表示)。
这样做会产生一个 (filterbanks_count x time_bins) 矩阵作为输出,结果,渲染的频谱图看起来很有趣(看起来多个频率仓被水平组合以填充可用宽度,正如我所期望的那样)。所以我的想法如下:我不计算
MEL(FFT) = MEL^t*FFT^t
而是计算MEL(FFT)^t = FFT*MEL
并得到正确的结果。
(请注意,Apple 的开发人员代码使用
windowSize
作为 B 矩阵行数的参数(代码中的filterBank
),因此 filterBank
矩阵只是关于上图中表示的滤波器组(转置))
这会产生以下代码:
cblas_sgemm(CblasRowMajor, //ORDER
CblasNoTrans, //Transpose A? if so, op(A) = A^t, else op(A) = A
CblasNoTrans, //Transpose B? if so, op(B) = B^t, else op(B) = B
Int32(fftResult.count/self.windowSize), //A and C's rows.
Int32(self.filterbanksCount), //B and C's cols.
Int32(self.windowSize), //A's cols, B's rows
1, //Scale A and B's product
fftResultPtr.baseAddress, //A
Int32(self.windowSize), //rows of op(A)^t
filterBank.baseAddress, //B
Int32(self.filterbanksCount), //rows of op(B)^t
0, //Result scale
sgemmResult!.baseAddress, //C
Int32(self.filterbanksCount) //rows of C^t
)
所以,如果我用上面的代码代替现有的代码,结果我得到一个频谱图,其中较低的频率占总高度的大约 20%,而较高的频率占剩余的 80%,这意味着假设将 Mel 标度应用于线性频谱图的效果被翻转了。
我的推理哪里失败了,我应该如何修复我的代码?