根据音色(音调)按相似度对声音进行排序
问题描述
说明
我希望能够根据声音的音色(音调)对列表中的声音集合进行排序。下面是一个玩具示例,其中我手动对我创建的12个声音文件和uploaded to this repo的频谱图进行了排序。我知道这些排序是正确的,因为为每个文件生成的声音与其前面文件中的声音完全相同,但添加了一种效果或过滤。
例如,声音的正确排序x
、y
和z
,其中
- x和y发音相同,但y有失真效果
- y和z发音相同,但z过滤掉高频
- 声音x和z相同,但z有失真效果,z过滤掉高频
将是x, y, z
仅通过查看光谱图,我就可以看到一些视觉指示器,它们提示应该如何对声音进行分类,但我希望通过让计算机识别这些指示器来自动执行分类过程。
上图中声音的声音文件
- 长度都相同
- 完全相同的音符/音调
- 所有操作完全同时开始。
- 所有相同的振幅(响度级别)
即使所有这些条件都不是true(但即使不能解决此问题,我也会接受最佳答案)
我也希望我的排序能够正常工作。例如,在下图中
- 与第一张图像中的MFCC_8相比,MFCC_8的起始位置移位
- MFCC_9与第一个映像中的MFCC_9相同,但是复制的(因此它的长度是第一个映像的两倍)
如果将第一个图像中的MFCC_8和MFCC_9替换为下图中的MFCC_8和MFCC_9,我希望声音的排序保持完全相同。
对于我的实际程序,我打算通过声音变化拆分mp3文件like this
我的程序到目前为止
这里是生成first image in this post的程序。我需要将函数sort_sound_files
中的代码替换为根据音色对声音文件进行实际排序的一些代码。需要做的部分在底部附近和sound files on on this repo。我在jupyter notebook中也有此代码,其中还包括第二个示例,该示例更类似于我实际希望此程序执行的操作
import librosa
import librosa.display
import matplotlib.pyplot as plt
import numpy as np
import math
from os import path
from typing import List
class Spec:
name: str = ''
sr: int = 44100
class MFCC(Spec):
mfcc: np.ndarray # Mel-frequency cepstral coefficient
delta_mfcc: np.ndarray # delta Mel-frequency cepstral coefficient
delta2_mfcc: np.ndarray # delta2 Mel-frequency cepstral coefficient
n_mfcc: int = 13
def __init__(self, soundFile: str):
self.name = path.basename(soundFile)
y, sr = librosa.load(soundFile, sr=self.sr)
self.mfcc = librosa.feature.mfcc(y, n_mfcc=self.n_mfcc, sr=sr)
self.delta_mfcc = librosa.feature.delta(self.mfcc, mode="nearest")
self.delta2_mfcc = librosa.feature.delta(self.mfcc, mode="nearest", order=2)
def get_mfccs(sound_files: List[str]) -> List[MFCC]:
'''
:param sound_files: Each item is a path to a sound file (wav, mp3, ...)
'''
mfccs = [MFCC(sound_file) for sound_file in sound_files]
return mfccs
def draw_specs(specList: List[Spec], attribute: str, title: str):
'''
Takes a list of same type audio features, and draws a spectrogram for each one
'''
def draw_spec(spec: Spec, attribute: str, fig: plt.Figure, ax: plt.Axes):
img = librosa.display.specshow(
librosa.amplitude_to_db(getattr(spec, attribute), ref=np.max),
y_axis='log',
x_axis='time',
ax=ax
)
ax.set_title(title + str(spec.name))
fig.colorbar(img, ax=ax, format="%+2.0f dB")
specLen = len(specList)
fig, axs = plt.subplots(math.ceil(specLen/3), 3, figsize=(30, specLen * 2))
for spec in range(0, len(specList), 3):
draw_spec(specList[spec], attribute, fig, axs.flat[spec])
if (spec+1 < len(specList)):
draw_spec(specList[spec+1], attribute, fig, axs.flat[spec+1])
if (spec+2 < len(specList)):
draw_spec(specList[spec+2], attribute, fig, axs.flat[spec+2])
sound_files_1 = [
'../assets/transients_1/4.wav',
'../assets/transients_1/6.wav',
'../assets/transients_1/1.wav',
'../assets/transients_1/11.wav',
'../assets/transients_1/13.wav',
'../assets/transients_1/9.wav',
'../assets/transients_1/3.wav',
'../assets/transients_1/7.wav',
'../assets/transients_1/12.wav',
'../assets/transients_1/2.wav',
'../assets/transients_1/5.wav',
'../assets/transients_1/10.wav',
'../assets/transients_1/8.wav'
]
mfccs_1 = get_mfccs(sound_files_1)
##################################################################
def sort_sound_files(sound_files: List[str]):
# TODO: Complete this function. The soundfiles must be sorted based on the content in the file, do not use the name of the file
# This is the correct order that the sounds should be sorted in
return [f"../assets/transients_1/{num}.wav" for num in range(1, 14)] # TODO: remove(or comment) once method is completed
##################################################################
sorted_sound_files_1 = sort_sound_files(sound_files_1)
mfccs_1 = get_mfccs(sorted_sound_files_1)
draw_specs(mfccs_1, 'mfcc', "Transients_1 Sorted MFCC-")
plt.savefig('sorted_sound_spectrograms.png')
编辑
我后来才意识到这一点,但另一件非常重要的事情是,会有很多性质在振荡。声音5和声音6与第一组的不同之处在于,例如声音6是声音5,但是由于音量上的振荡(LFO),这种类型的振荡可以置于频率过滤、效果(如失真)或甚至音调上。我意识到这使得问题变得更加棘手,这超出了我所问的范围。你有什么建议吗?我甚至可以使用几种不同的排序,并且一次只查看一个属性。解决方案
我想出了一个方法,不确定它是否完全符合您的期望,但是对于您的第一个数据集,它非常接近。基本上,我查看的是.wav
文件的功率谱密度的power spectral density,并按其归一化积分进行排序。(我没有很好的信号处理理由这样做。PSD可以让你了解每个频率有多少能量。我最初试着按PSD分类,结果很差。认为在处理文件时会产生更多的可变性,我认为这会以这种方式改变光谱密度的变化,于是就试了试。)如果这能满足您的需要,我希望您能找到该方法的理由。
第一步:
这非常简单,只需将y
更改为self.y
即可将其添加到MFCC
类:
class MFCC(Spec):
mfcc: np.ndarray # Mel-frequency cepstral coefficient
delta_mfcc: np.ndarray # delta Mel-frequency cepstral coefficient
delta2_mfcc: np.ndarray # delta2 Mel-frequency cepstral coefficient
n_mfcc: int = 13
def __init__(self, soundFile: str):
self.name = path.basename(soundFile)
self.y, sr = librosa.load(soundFile, sr=self.sr) # <--- This line is changed
self.mfcc = librosa.feature.mfcc(self.y, n_mfcc=self.n_mfcc, sr=sr)
self.delta_mfcc = librosa.feature.delta(self.mfcc, mode="nearest")
self.delta2_mfcc = librosa.feature.delta(self.mfcc, mode="nearest", order=2)
第二步: 计算PSD的PSD并积分(或实际上只是求和):
def spectra_of_spectra(mfcc):
# first calculate the psd
fft = np.fft.fft(mfcc.y)
fft = fft[:len(fft)//2+1]
psd1 = np.real(fft * np.conj(fft))
# then calculate the psd of the psd
fft = np.fft.fft(psd1/sum(psd1))
fft = fft[:len(fft)//2+1]
psd = np.real(fft * np.conj(fft))
return(np.sum(psd)/len(psd))
除以长度(标准化)有助于比较不同长度的不同文件。
第三步: 排序
def sort_mfccs(mfccs):
values = [spectra_of_spectra(mfcc) for mfcc in mfccs]
sorted_order = [i[0] for i in sorted(enumerate(values), key=lambda x:x[1], reverse = True)]
return([i for i in sorted_order], [values[i] for i in sorted_order])
测试
mfccs_1 = get_mfccs(sound_files_1)
sort_mfccs(mfccs_1)
1.wav
2.wav
3.wav
4.wav
5.wav
6.wav
7.wav
8.wav
9.wav
10.wav
12.wav
11.wav
13.wav
请注意,除11.wav
和12.wav
外,文件按您预期的方式排序。
我不确定您是否同意第二组文件的订单。我想这是对我的方法有多有用的测试。
mfccs_2 = get_mfccs(sorted_sound_files_2)
sort_mfccs(mfccs_2)
12.wav
22.wav
26.wav
31.wav
4.wav
13.wav
34.wav
30.wav
21.wav
23.wav
7.wav
38.wav
11.wav
3.wav
9.wav
36.wav
16.wav
17.wav
33.wav
37.wav
8.wav
28.wav
5.wav
25.wav
20.wav
1.wav
39.wav
29.wav
18.wav
0.wav
27.wav
14.wav
35.wav
15.wav
24.wav
10.wav
19.wav
32.wav
2.wav
6.wav
代码中最后一个问题:UserWarning
我不熟悉您在这里使用的模块,但它似乎正在尝试对一个长度为1536的文件执行窗口长度为2048的FFT。快速傅立叶变换是任何类型的频率分析的基础。在您的self.mfcc = librosa.feature.mfcc(self.y, n_mfcc=self.n_mfcc, sr=sr)
行中,您可以指定kwargn_fft
来删除它,例如n_fft = 1024
。但是,我不确定为什么librosa
使用2048作为默认设置,因此您可能希望在更改之前仔细检查。
编辑
绘制这些值将有助于更多地显示比较结果。值的差异越大,文件中的差异就越大。
def diff_matrix(L, V, mfccs):
plt.figure()
plt.semilogy(V, '.')
for i in range(len(V)):
plt.text(i, V[i], mfccs[L[i]].name.split('.')[0], fontsize = 8)
plt.xticks([])
plt.ylim([0.001, 1])
plt.ylabel('Value')
这是您的第一组测试结果
和第二套
根据这些值彼此之间的接近程度(考虑百分比变化而不是差异),与第一个相比,第二个集合的排序对任何调整都非常敏感。
编辑%2
我对你下面的回答的最好尝试是尝试一下这样的东西。为简单起见,我将音调频率描述为音符的频率,将频谱频率描述为从信号处理的角度来看的频率变化。我希望这是有道理的。我预计音量的振荡将达到所有音调,因此对PSD的贡献将取决于音量如何根据频谱频率进行振荡。当不同的音调频率受到不同的阻尼时,你需要开始考虑哪些音调频率对你所做的事情是重要的。我认为我的排序之所以在您的第一个示例中如此成功,可能是因为音调频率之间的差异无处不在(或几乎无处不在)。也许有一种方法可以考虑在不同的音调频率或音调频带下观察PSD。我还没有完全吸收另一个答案中提到的论文中的信息,但是如果你理解数学的话,我可以从那里开始。作为免责声明,我只是随便玩玩,编造了一些东西来试图回答你的问题。您可能需要考虑就site more focused on questions like this提出后续问题。相关文章