Сортировка звуков по сходству на основе тембра (тона)

#python #audio #librosa

#python #Аудио #обработка сигналов #numpy-ndarray #librosa

Вопрос:

Объяснение

Я хочу иметь возможность сортировать набор звуков в списке на основе тембра (тона) звука. Вот игрушечный пример, в котором я вручную отсортировал спектрограммы для 12 звуковых файлов, которые я создал и загрузил в это хранилище. Я знаю, что они отсортированы правильно, потому что звук, создаваемый для каждого файла, точно такой же, как звук в предыдущем файле, но с добавлением к нему одного эффекта или фильтра.

Например, правильная сортировка звуков x , y и z где

  • звуки 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-файл на звуковые изменения, подобные этому


Моя программа до сих пор

Вот программа, которая создает первое изображение в этом сообщении. Мне нужно, чтобы код в функции sort_sound_files был заменен некоторым кодом, который фактически сортирует звуковые файлы на основе тембра. Часть, которую необходимо выполнить, находится внизу, а звуковые файлы находятся в этом репозитории. У меня также есть этот код в записной книжке jupyter, который также включает второй пример, который больше похож на то, что я на самом деле хочу, чтобы эта программа делала

 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) этот тип колебаний может быть помещен в частотный фильтр, эффект (например, искажение) или даже высота тона. Я понимаю, что это делает проблему намного сложнее, и это выходит за рамки того, что я спросил. У вас есть какие-либо советы? Я мог бы даже использовать несколько разных видов и одновременно просматривать только одно свойство.

Комментарии:

1. Если вы хотите основывать сходство на характеристиках звука, который не является нотой, это обычно называют тембром

2. @jonnor Да. Мне интересно, как записать тембр в формат, который я могу использовать. Как массив или что-то в этом роде, и что будут представлять строки / столбцы в массиве

3. В целом, тембр — довольно сложная концепция восприятия, и его немного сложно отделить от нот аудио. Лучше всего было бы использовать некоторую изученную модель / встраивание, которая отображает ее в пространство меньшего размера только для тембра

4. @jonnor Что именно вы имеете в виду, когда говорите «отображает его в пространство меньшего размера». Я немного больше читал об этом, и похоже, что функции MFCC хороши для сравнения тембра. Есть ли у вас рекомендации относительно того, какую модель я мог бы использовать для отображения этого?

5. Что вывод представляет собой несколько чисел, возможно, 2-10, которые представляют только тембр, то есть отделяют его / не зависят от самого мюзикла. В MFCC обе вещи все еще запутаны. К сожалению, на данный момент я не знаю модели для этого.

Ответ №1:

Сэм, я думаю, что вы можете сравнить две картинки с помощью машинного обучения или, может быть, с помощью 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 файлов и сортирую по нормализованному интегралу от этого. (У меня нет веской причины для обработки сигналов для этого. 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.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
  

отсортированные результаты

Последний момент, касающийся вопроса в коде re: UserWarning

Я не знаком с модулем, который вы используете здесь, но похоже, что он пытается выполнить БПФ с длиной окна 2048 в файле длиной 1536. БПФ являются строительным блоком любого вида частотного анализа. В вашей строке 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')
  

Вот результаты для вашего первого набора

diff1

и второй набор

diff2

В зависимости от того, насколько близки значения относительно друг друга (думайте о% изменении, а не о разнице), сортировка второго набора будет довольно чувствительной к любым изменениям по сравнению с первым.

РЕДАКТИРОВАТЬ 2

Моим лучшим ответом на ваш ответ ниже было бы попробовать что-то подобное. Для простоты я собираюсь описать частоту основного тона как частоту ноты, а спектральную частоту — как изменения частоты с точки зрения обработки сигнала. Я надеюсь, что это имеет смысл.

Я бы ожидал, что колебания громкости достигнут всех тонов, и поэтому вклад в PSD будет зависеть от того, как громкость колеблется в терминах спектральных частот. Когда разные частоты основного тона затухают по-разному, вам нужно будет начать думать о том, какие частоты основного тона важны для того, что вы делаете. Я думаю, причина, по которой моя сортировка была настолько успешной в вашем первом примере, вероятно, заключается в том, что вариации были повсеместными (или почти повсеместными) на частотах основного тона. Возможно, есть способ рассмотреть возможность просмотра PSD на разных частотах основного тона или диапазонах частот основного тона. Я не полностью усвоил информацию в документе, на который ссылается другой ответ, но если вы понимаете математику, я бы начал с этого. В качестве отказа от ответственности я просто поиграл и придумал что-то, чтобы попытаться ответить на ваш вопрос. Возможно, вы захотите задать дополнительный вопрос на сайте, более ориентированном на подобные вопросы.

Комментарии:

1. Я не осознавал этого раньше, но еще одна довольно важная вещь заключается в том, что будет много свойств, которые колеблются. Разница между звуком 5 и звуком 6 из первого набора, например, заключается в том, что звук 6 — это звук 5, но с колебаниями громкости (LFO) этот тип колебаний может быть помещен в частотный фильтр, эффект (например, искажение) или даже высота тона. Я понимаю, что это делает проблему намного сложнее, и это выходит за рамки того, что я спросил. У вас есть какие-либо советы? Я мог бы даже использовать несколько разных видов и одновременно просматривать только одно свойство.

2. Кстати, ваш ответ отличный, я собираюсь оставить вопрос открытым до конца, чтобы посмотреть, какие другие ответы я получу, но это полезно

3. @Sam, я попытался ответить на ваш вопрос в другом редактировании моего вопроса. Я хотел бы дать вам больше информации, чем это, но на самом деле я не эксперт в обработке сигналов или технических аспектах звука / музыки.

4. Что такое L и V внутри diff_matrix(L, V, mfccs):

5. Просто оглядываясь назад на мой код, я думаю, что это результат sort_matrices , т. е. L,V = sort_mfccs(mfccs) .

Ответ №4:

Интересный вопрос. Вы можете обнаружить, что тембр — это несколько сложная величина, которую не так легко определить с помощью одного числа. Однако в некоторых исследованиях пытались извлечь так называемые «числовые параметры» тембра звуков, чтобы сгруппировать и сравнить.

Например, такие исследования: Geoffroy Peeters, 2011, The Timbre Toolbox: извлечение звуковых дескрипторов из музыкальных сигналов.

Внутри документа (который должен быть в свободном доступе) вы найдете различные значения звука, и вы увидите, что тембр также выходит за пределы спектральной области. Однако, чтобы указать вам подходящее направление, я бы посмотрел на «Спектральный центроид» и «Спектральный разброс». С точки зрения вычисления расстояния это можно сделать несколькими способами, полагая, что звуки находятся в многомерном пространстве параметров тембра.

Вот список ссылок на соответствующие части librosa :

Вы можете сделать это либо для полного звукового файла, либо для того, что подходит вам по назначению 🙂

Комментарии:

1. Интересно! Спасибо, что поделились. Из того, что я могу сказать в документе и коде, spectral_centroid должно быть локальным целым числом нормализованного квадрата спектральной плотности мощности. Из любопытства, знаете ли вы, почему нужно использовать амплитуду (квадрат PSD), а не энергию (связанную с квадратом амплитуды)? Это что-то о том, как работает ваше ухо? Как бы вы выбрали порядок p spectral_bandwidth ? (Математика и физика музыки — это не совсем моя область знаний!)

2. Из статьи Питерса они используют $ p_k $ в качестве нормализованной версии «либо величины STFT, либо мощности STFT, гармонических синусоидальных частичных значений, либо выходных данных модели ERB». Я не понимаю, почему они выбрали конкретный спектр librosa — я думаю, для простоты использования. В документе Питерса они выбирают $ 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. Вы можете перечислить один и тот же файл или каталог дважды.

При обнаружении ошибок будут выведены соответствующие сообщения об ошибках, и программа может продолжить, если сможет. Результаты совпадения будут напечатаны как «НЕТ СОВПАДЕНИЯ», если сравнивались два несовпадающих файла, и «СОВПАДЕНИЕ …», если сравнивались два совпадающих файла, со списком двух совпадающих файлов и оценкой соответствия.

Ссылка: https://github.com/charlesconnell/AudioCompare

Комментарии:

1. Файлы не являются производными друг от друга, у меня есть программное обеспечение для синтезатора, и, начиная со знаковой волны для звука 1, звук 2 — это знаковая волна с добавленным к ней эффектом, звук 3 — это звук 2 с другим эффектом, затем я написал midi-трек, который воспроизводит ноту для 4 ударови установите, чтобы каждый звук начинался с начала такта 1, прежде чем экспортировать каждый звук в файл wav / mp3. Этот метод предназначен только для моего игрушечного примера, потому что я знал, что это будет проще. Я намерен разбивать mp3-файлы по изменениям заметок следующим образом