根据音色(音调)按相似度对声音进行排序

Posted

技术标签:

【中文标题】根据音色(音调)按相似度对声音进行排序【英文标题】:Sort sounds by similarity based on timbre(tone) 【发布时间】:2020-10-28 20:05:00 【问题描述】:

说明

我希望能够根据声音的音色(音调)对列表中的一组声音进行排序。这是一个玩具示例,我手动对我创建的 12 个声音文件和uploaded to this repo 的频谱图进行排序。我知道这些排序正确,因为每个文件产生的声音与之前文件中的声音完全相同,但添加了一个效果或过滤器。

例如,正确排序声音 xyz where

听起来 x 和 y 是一样的,但是 y 有失真效果 y 和 z 的发音相同,但 z 会滤除高频 听起来x和z是一样的,但是z有失真效果,z过滤掉高频

应该是x, y, z

仅通过查看频谱图,我可以看到一些提示声音应该如何排序的视觉指标,但我希望通过让计算机识别这些指标来自动化排序过程。


上图中声音的声音文件

长度都一样 所有相同的音符/音高 一切都在同一时间开始。 幅度相同(响度级别)

即使所有这些条件为真,我也希望我的排序能够正常工作(但即使不能解决这个问题,我也会接受最佳答案)

比如下图

与第一张图片中的 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),这种类型的振荡可以放在频率滤波器上,效果(如失真)甚至音高。我意识到这使问题变得更加棘手,并且超出了我所问的范围。你有什么建议吗?我什至可以使用几种不同的类型,并且一次只查看一个属性。

【问题讨论】:

如果你想根据不是音符的声音的特征来建立相似性,通常称为音色 @jonnor 是的。我想知道如何将音色录制成我可以使用的格式。比如数组什么的,数组中的行/列代表什么 音色是一个相当复杂的感知概念,一般来说 - 并且有点难以与音频的音符分离。最好的办法是使用一些学习模型/嵌入,将其映射到仅音色的低维空间 @jonnor 当您说“将其映射到较低维度的空间”时,您到底是什么意思。我已经阅读了更多关于此的内容,看起来 MFCC 功能非常适合比较音色。你对我可以用什么模型来映射这个有什么建议吗? 输出是几个数字,可能是 2-10,仅代表音色 - 将它分开/独立于音乐而不是本身。在 MFCC 中,这两件事仍然纠缠不清。不幸的是,目前我不知道有什么模型。 【参考方案1】:

Sam,我认为你可以用机器学习比较两张图片,或者用 numpy 作为数据数组。

这只是解决方案的一个想法(不是完整的答案): 是否可以将两个直方图转换为大小相等的平面数组 numpy.ndarray.flatten

array1 = numpy.array([1.1, 2.2, 3.3])
array2 = numpy.array([1, 2, 3])
diffs = array1 - array2 # array([ 0.1,  0.2,  0.3])
similarity_coefficient = np.sum(diffs)

【讨论】:

【参考方案2】:

这个https://github.com/AudioCommons/timbral_models 包预测了八种音色特征:硬度、深度、亮度、粗糙度、温暖度、锐度、隆隆声和混响。

我按它们中的每一个排序。

from timbral_models import timbral_extractor
from pathlib import Path
from operator import itemgetter

path = Path("sort-sounds-by-similarity-from-sound-file/assets/transients_1/")
timbres = [
    "file": file, "timbre": timbral_extractor(str(file)) for file in path.glob("*wav")
]

itemgetters = key: itemgetter(key) for key in timbres[0]["timbre"]

for timbre, get_timbre in itemgetters.items():
    print(f"Sorting by timbre")
    for item in sorted(timbres, key=lambda d: get_timbre(d["timbre"])):
        print(item["file"].name)
    print()

输出;

Sorting by hardness
1.wav
2.wav
6.wav
3.wav
4.wav
13.wav
7.wav
9.wav
8.wav
10.wav
5.wav
11.wav
12.wav

Sorting by depth
4.wav
12.wav
5.wav
6.wav
9.wav
8.wav
7.wav
3.wav
10.wav
11.wav
2.wav
1.wav
13.wav

Sorting by brightness
1.wav
2.wav
3.wav
9.wav
10.wav
6.wav
5.wav
8.wav
7.wav
4.wav
13.wav
11.wav
12.wav

Sorting by roughness
3.wav
1.wav
2.wav
7.wav
8.wav
9.wav
5.wav
6.wav
4.wav
10.wav
13.wav
11.wav
12.wav

Sorting by warmth
7.wav
6.wav
8.wav
12.wav
9.wav
11.wav
4.wav
5.wav
10.wav
13.wav
2.wav
3.wav
1.wav

Sorting by sharpness
1.wav
3.wav
2.wav
10.wav
9.wav
5.wav
7.wav
6.wav
8.wav
13.wav
4.wav
11.wav
12.wav

Sorting by boominess
8.wav
9.wav
6.wav
5.wav
4.wav
7.wav
12.wav
2.wav
3.wav
10.wav
1.wav
11.wav
13.wav

Sorting by reverb
12.wav
11.wav
9.wav
13.wav
6.wav
8.wav
7.wav
10.wav
4.wav
3.wav
2.wav
1.wav
5.wav

【讨论】:

【参考方案3】:

我想出了一个方法,不确定它是否完全符合您的期望,但对于您的第一个数据集,它非常接近。基本上,我正在查看您的.wav 文件的功率谱密度的power spectral density,并按其归一化积分进行排序。 (我没有很好的信号处理理由这样做。PSD 让您了解每个频率有多少能量。我最初尝试按 PSD 排序并得到不好的结果。认为当您处理文件时,您正在创建更多可变性,我认为这会以这种方式改变光谱密度的变化并尝试了它。)如果这满足您的需要,我希望您能找到该方法的理由。

第 1 步: 这非常简单,只需将 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)

第 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))

除以长度(归一化)有助于比较不同长度的不同文件。

第 3 步: 排序

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.wav12.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。FFT 是任何类型的构建块频率分析。在 self.mfcc = librosa.feature.mfcc(self.y, n_mfcc=self.n_mfcc, sr=sr) 行中,您可以指定 kwarg n_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 上提出后续问题。

【讨论】:

我直到后来才意识到这一点,但另一个非常重要的事情是会有很多属性在振荡。例如,第一组声音 5 和声音 6 之间的区别在于声音 6 是声音 5,但在音量上有振荡(LFO),这种类型的振荡可以放在频率滤波器上,效果(如失真)甚至音高。我意识到这使问题变得更加棘手,并且超出了我所问的范围。你有什么建议吗?我什至可以使用几种不同的类型,并且一次只查看一个属性。 顺便说一句,您的回答很棒,我将把问题留到最后,看看我得到了哪些其他答案,但这很有用 @Sam,我试图在我的问题的另一个编辑中解决您的问题。我希望我能给你更多的信息,但我真的不是信号处理或声音/音乐技术方面的专家。【参考方案4】:

有趣的问题。你可能会发现音色是一个有点复杂的量,它不是那么容易用一个数字来量化的。 然而,一些研究试图提取声音音色的所谓“数值参数”,以便进行分组和比较。

此类研究例如:Geoffroy Peeters, 2011, The Timbre Toolbox: Extracting audio descriptors from musical signals。

在论文中(应该可以免费获得),您会发现声音的各种量,并且您会看到音色也扩展到频谱域之外。但是,为了给你指出一个合适的方向,我会看看“光谱质心”和“光谱传播”。在计算距离方面,这可以通过多种方式完成,将声音视为驻留在音色参数的多维空间中。

这里是librosa相关部分的链接列表:

librosa.feature.spectral_centroid librosa.feature.spectral_bandwidth

您可以为完整的声音文件执行此操作,或者适合您的目的:-)

【讨论】:

有趣!感谢分享。从我在论文和代码中可以看出,spectral_centroid 应该是功率谱密度的归一化平方根的局部整数。出于好奇,您知道为什么要使用幅度(PSD 的平方根)而不是能量(与幅度平方相关)吗?与您的耳朵如何工作有关吗?你会如何选择pspectral_bandwidth 的顺序? (音乐的数学和物理并不完全是我的专长!) 从 Peeters 论文中,他们使用 $p_k$ 作为“幅度 STFT、功率 STFT、谐波正弦部分或 ERB 模型输出”的归一化版本。我不知道他们为什么选择librosa 中的特定频谱——我的猜测是为了便于使用。在 Peeters 论文中,他们选择 $p=2$ 作为订单。我不知道这和听觉系统有什么关系。【参考方案5】:

比较两个音频文件或音频文件目录以衡量它们的相似性。可能源自另一个文件的文件被标记为匹配项。

要运行程序,请键入以下之一:

./audiocompare -f file1 -f file2
./audiocompare -f file1 -d dir1
./audiocompare -d dir1 -f file1
./audiocompare -d dir1 -d dir2

“-f”参数后面的参数必须是文件名,“-d”参数后面的参数必须是仅包含音频文件的目录。输入文件必须是 WAVE 或 MP3 文件。您可以列出两次相同的文件或目录。

如果发现错误,将打印相应的错误消息,如果可以,程序可能会继续运行。如果比较了两个不匹配的文件,匹配结果将打印为“NO MATCH”,如果比较了两个匹配的文件,则打印为“MATCH ...”,列出匹配的两个文件,并给出匹配分数。

链接:https://github.com/charlesconnell/AudioCompare

【讨论】:

文件不是相互派生的,我有一个合成器软件,从声音 1 的符号波开始,声音 2 是添加了效果的符号波,声音 3 是声音 2 和另一种效果,然后我写了一个 MIDI 轨道,播放一个音符 4 拍,并将每个声音设置为从第 1 拍的开头开始,然后将每个声音导出到 wav/mp3 文件。不过,此方法仅适用于我的玩具示例,因为我知道它会更容易。 I intend to break up mp3 files by the note changes like this

以上是关于根据音色(音调)按相似度对声音进行排序的主要内容,如果未能解决你的问题,请参考以下文章

有没有一种根据 Jaccard 相似度对图进行聚类的有效方法?

使用余弦相似度对文档进行分类

使用 Jaccard 相似度对分类数据进行聚类

NLPPython实例:基于文本相似度对申报项目进行查重设计

R语言计算杰卡德相似系数(Jaccard Similarity)实战:自定义函数计算Jaccard相似度对字符串向量计算Jaccard相似度将Jaccard相似度转化为Jaccard距离

获取最相似的行并按相似度排序 - 性能改进