切片音频信号以检测音高
Slicing audio signal to detect pitch
我正在使用 Librosa 转录单声道吉他音频信号。
我认为,"slice" 根据起始时间的信号,在正确的时间检测音符变化是一个好的开始。
Librosa 提供了一个 function 来检测起始时间之前的局部最小值。我检查了那些时间,它们是正确的。
这里是原始信号的波形和最小值的次数。
[ 266240 552960 840704 1161728 1427968 1735680 1994752]
播放的旋律是E4,F4,F#4 ...,B4。
因此理想的结果应该是:330Hz、350Hz、...、493Hz(大约)。
如您所见,minima
数组中的时间表示音符播放之前的时间。
然而,在切片信号上(10-12 秒,每个切片只有一个音符),我的频率检测方法的结果非常差。我很困惑,因为我在我的代码中看不到任何错误:
y, sr = librosa.load(filename, sr=40000)
onset_frames = librosa.onset.onset_detect(y=y, sr=sr)
oenv = librosa.onset.onset_strength(y=y, sr=sr)
onset_bt = librosa.onset.onset_backtrack(onset_frames, oenv)
# Converting those times from frames to samples.
new_onset_bt = librosa.frames_to_samples(onset_bt)
slices = np.split(y, new_onset_bt[1:])
for i in range(0, len(slices)):
print freq_from_hps(slices[i], 40000)
print freq_from_autocorr(slices[i], 40000)
print freq_from_fft(slices[i], 40000)
其中 freq_from
函数直接取自 here。
我认为这只是方法的精度不佳,但我得到了一些疯狂的结果。具体来说,freq_from_hps
returns:
1.33818658287
1.2078047577
0.802142642257
0.531096911977
0.987532329094
0.559638134414
0.953497587952
0.628980979055
这些值应该是 8 个对应切片的 8 个音高(以 Hz 为单位!)。
freq_from_fft
returns 相似的值,而 freq_from_autocorr
returns 更多 "normal" 值,但也有一些接近 10000Hz 的随机值:
242.748000585
10650.0394232
275.25299319
145.552578747
154.725859019
7828.70876515
174.180627765
183.731497068
这是整个信号的频谱图:
例如,切片 1(E4 音符)的频谱图:
如您所见,切片已正确完成。但是,存在几个问题。首先,频谱图中存在八度问题。我期待一些问题。然而,我从上面提到的3种方法得到的结果非常奇怪。
这是我对信号处理的理解或我的代码的问题吗?
Is this an issue with my signal processing understanding or my code?
我觉得你的代码没问题。
你要检测的频率是你音高的基频(这个问题也被称为"f0 estimation")。
因此,在使用 freq_from_fft
之类的东西之前,我会对信号进行带通滤波,以去除垃圾瞬变和低频噪声——信号中存在但与您的问题无关的东西。
想一想,您的基频将在哪个范围内。对于 E2 (82 Hz) 至 F6 (1,397 Hz) 的原声吉他。这意味着您可以摆脱低于 ~80 Hz 和高于 ~1,400 Hz 的任何东西(有关带通示例,请参见 here)。过滤后,进行峰值检测以找到音调(假设基波实际上具有最大能量)。
另一种策略可能是忽略每个切片的第一个 X
样本,因为它们本质上往往是打击乐而不是和声,并且不会给你太多信息。所以,在你的切片中,只看最后 ~90% 的样本。
总而言之,f0 或基频估计有大量工作要做。 ISMIR 篇论文是一个很好的起点。
最后但同样重要的是,Librosa 的 piptrack 功能可以满足您的需求。
我正在使用 Librosa 转录单声道吉他音频信号。
我认为,"slice" 根据起始时间的信号,在正确的时间检测音符变化是一个好的开始。
Librosa 提供了一个 function 来检测起始时间之前的局部最小值。我检查了那些时间,它们是正确的。
这里是原始信号的波形和最小值的次数。
[ 266240 552960 840704 1161728 1427968 1735680 1994752]
播放的旋律是E4,F4,F#4 ...,B4。
因此理想的结果应该是:330Hz、350Hz、...、493Hz(大约)。
如您所见,minima
数组中的时间表示音符播放之前的时间。
然而,在切片信号上(10-12 秒,每个切片只有一个音符),我的频率检测方法的结果非常差。我很困惑,因为我在我的代码中看不到任何错误:
y, sr = librosa.load(filename, sr=40000)
onset_frames = librosa.onset.onset_detect(y=y, sr=sr)
oenv = librosa.onset.onset_strength(y=y, sr=sr)
onset_bt = librosa.onset.onset_backtrack(onset_frames, oenv)
# Converting those times from frames to samples.
new_onset_bt = librosa.frames_to_samples(onset_bt)
slices = np.split(y, new_onset_bt[1:])
for i in range(0, len(slices)):
print freq_from_hps(slices[i], 40000)
print freq_from_autocorr(slices[i], 40000)
print freq_from_fft(slices[i], 40000)
其中 freq_from
函数直接取自 here。
我认为这只是方法的精度不佳,但我得到了一些疯狂的结果。具体来说,freq_from_hps
returns:
1.33818658287
1.2078047577
0.802142642257
0.531096911977
0.987532329094
0.559638134414
0.953497587952
0.628980979055
这些值应该是 8 个对应切片的 8 个音高(以 Hz 为单位!)。
freq_from_fft
returns 相似的值,而 freq_from_autocorr
returns 更多 "normal" 值,但也有一些接近 10000Hz 的随机值:
242.748000585
10650.0394232
275.25299319
145.552578747
154.725859019
7828.70876515
174.180627765
183.731497068
这是整个信号的频谱图:
例如,切片 1(E4 音符)的频谱图:
如您所见,切片已正确完成。但是,存在几个问题。首先,频谱图中存在八度问题。我期待一些问题。然而,我从上面提到的3种方法得到的结果非常奇怪。
这是我对信号处理的理解或我的代码的问题吗?
Is this an issue with my signal processing understanding or my code?
我觉得你的代码没问题。
你要检测的频率是你音高的基频(这个问题也被称为"f0 estimation")。
因此,在使用 freq_from_fft
之类的东西之前,我会对信号进行带通滤波,以去除垃圾瞬变和低频噪声——信号中存在但与您的问题无关的东西。
想一想,您的基频将在哪个范围内。对于 E2 (82 Hz) 至 F6 (1,397 Hz) 的原声吉他。这意味着您可以摆脱低于 ~80 Hz 和高于 ~1,400 Hz 的任何东西(有关带通示例,请参见 here)。过滤后,进行峰值检测以找到音调(假设基波实际上具有最大能量)。
另一种策略可能是忽略每个切片的第一个 X
样本,因为它们本质上往往是打击乐而不是和声,并且不会给你太多信息。所以,在你的切片中,只看最后 ~90% 的样本。
总而言之,f0 或基频估计有大量工作要做。 ISMIR 篇论文是一个很好的起点。
最后但同样重要的是,Librosa 的 piptrack 功能可以满足您的需求。