8. Ezgi Analizi - Temel titreşim frekans kestirimi#

Open In Colab

8.1. Ezgi Analizi - Temel Titreşim Frekans Kestirimi#

Müzik sinyallerinde en önemli boyutlardan birisi ezgi boyutudur. İlk olarak 5. defterimizde spektrogramdan kromagram temsili elde ederek bir tür (oktav olarak katlanmış) ezgi temsili elde etmiştik. Bu defterimizde hedefimiz bir kayıt içerisinde icra edilen frekansları kestirmek (İng: estimate) için fonksiyon yazmak. Anlaşılabilir bir içerik sunabilmek için bu defterde bir anda sadece tek notanın icra edildiği (monofonik) kayıtları ele alacağız.

Önceki defterlerimizde ses sinyalinin karakteristiğinin zamanla değiştiğini ve bu nedenle sinyalimizi küçük parçalara ayırarak analiz yapmayı tercih ettiğimizi belirtmiştik. Örneğin 6. defterimizde sinyalimizi ardışık küçük kesitlere/pencerelere ayırmış ve her bir kesit için parametre (enerji, sıfır kesim oranı, spektral merkez, vb.) kestirimi gerçekleştirmiştik. Bu defterimizde de aynı şekilde sinyallerimizi küçük kesitlere ayıracak ve her küçük kesit için bir temel titreşim frekans değeri kestirmeye çalışacağız.

Temel titreşim frekansı (İng: fundamental frequency), monofonik bir ses sinyalinden alınan bir kesitte ana dalga formunun saniyedeki tekrar sayısı olarak düşünülebilir. Kütüphanelerimizi yükleyelim ve daha sonra bir ses örneği yükleyip inceleyelim.

import os
import librosa
import soundfile as sf
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import get_window
import zipfile
import urllib.request
from IPython.display import Audio
# Ses verisi okuma ve Melodia aracı ile frekans kestirimi için Essentia paketinin kurulumu ve yüklenmesi
import importlib.util
if importlib.util.find_spec('essentia') is None:
  !pip install essentia
import essentia.standard as ess
[   INFO   ] MusicExtractorSVM: no classifier models were configured by default

İncelemek için bir dizi ses kaydı indirelim.

# UIOWA:MIS veri kümesi bağlantı adresleri
baglantilar = {
    'flute': 'http://theremin.music.uiowa.edu/sound%20files/MIS/Woodwinds/flute/flute.nonvib.ff.zip',
}

veri_klasoru = 'enstruman'
if not os.path.exists(veri_klasoru):  # klasör yok ise oluşturalım
    os.mkdir(veri_klasoru)
for enstruman, url in baglantilar.items():
    print(enstruman, 'için dosyaları indiriyor')
    hedef_klasor = os.path.join(veri_klasoru, enstruman)
    # enstrüman için altklasör yok ise oluşturalım
    if not os.path.exists(hedef_klasor):
        os.mkdir(hedef_klasor)
    dosya_ismi = url.split('/')[-1]
    urllib.request.urlretrieve(url, dosya_ismi)
    # Zip dosyasının açılması
    zip_ref = zipfile.ZipFile(dosya_ismi, 'r')
    zip_ref.extractall(hedef_klasor)
    zip_ref.close()
    os.remove(dosya_ismi)  # açılmış zip dosyasının silinmesi
    print('Veri indirildi, açıldı ve yerleştirildi: ', hedef_klasor)
flute için dosyaları indiriyor
Veri indirildi, açıldı ve yerleştirildi:  enstruman/flute

Standart nota frekanslarını da bir tablodan okuyalım.

import pandas as pd # csv dosyasını okumak için pandas kütüphanesini kullanalım
nota_fr_tablo = pd.read_csv('https://github.com/barisbozkurt/dataDumpForCourses/raw/master/noteFrequencies.csv')
# Tabloyu pandas-dataFrame'den kütüphaneye dönüştürelim
nota_frekanslari = {nota_fr_tablo.iloc[ind]['notes']:nota_fr_tablo.iloc[ind]['frequencies'] for ind in range(3,nota_fr_tablo.shape[0])}
print(nota_frekanslari)
{'C0': 16.35, 'C#0': 17.32, 'Db0': 17.32, 'D0': 18.35, 'D#0': 19.45, 'Eb0': 19.45, 'E0': 20.6, 'F0': 21.83, 'F#0': 23.12, 'Gb0': 23.12, 'G0': 24.5, 'G#0': 25.96, 'Ab0': 25.96, 'A0': 27.5, 'A#0': 29.14, 'Bb0': 29.14, 'B0': 30.87, 'C1': 32.7, 'C#1': 34.65, 'Db1': 34.65, 'D1': 36.71, 'D#1': 38.89, 'Eb1': 38.89, 'E1': 41.2, 'F1': 43.65, 'F#1': 46.25, 'Gb1': 46.25, 'G1': 49.0, 'G#1': 51.91, 'Ab1': 51.91, 'A1': 55.0, 'A#1': 58.27, 'Bb1': 58.27, 'B1': 61.74, 'C2': 65.41, 'C#2': 69.3, 'Db2': 69.3, 'D2': 73.42, 'D#2': 77.78, 'Eb2': 77.78, 'E2': 82.41, 'F2': 87.31, 'F#2': 92.5, 'Gb2': 92.5, 'G2': 98.0, 'G#2': 103.83, 'Ab2': 103.83, 'A2': 110.0, 'A#2': 116.54, 'Bb2': 116.54, 'B2': 123.47, 'C3': 130.81, 'C#3': 138.59, 'Db3': 138.59, 'D3': 146.83, 'D#3': 155.56, 'Eb3': 155.56, 'E3': 164.81, 'F3': 174.61, 'F#3': 185.0, 'Gb3': 185.0, 'G3': 196.0, 'G#3': 207.65, 'Ab3': 207.65, 'A3': 220.0, 'A#3': 233.08, 'Bb3': 233.08, 'B3': 246.94, 'C4': 261.63, 'C#4': 277.18, 'Db4': 277.18, 'D4': 293.66, 'D#4': 311.13, 'Eb4': 311.13, 'E4': 329.63, 'F4': 349.23, 'F#4': 369.99, 'Gb4': 369.99, 'G4': 392.0, 'G#4': 415.3, 'Ab4': 415.3, 'A4': 440.0, 'A#4': 466.16, 'Bb4': 466.16, 'B4': 493.88, 'C5': 523.25, 'C#5': 554.37, 'Db5': 554.37, 'D5': 587.33, 'D#5': 622.25, 'Eb5': 622.25, 'E5': 659.25, 'F5': 698.46, 'F#5': 739.99, 'Gb5': 739.99, 'G5': 783.99, 'G#5': 830.61, 'Ab5': 830.61, 'A5': 880.0, 'A#5': 932.33, 'Bb5': 932.33, 'B5': 987.77, 'C6': 1046.5, 'C#6': 1108.73, 'Db6': 1108.73, 'D6': 1174.66, 'D#6': 1244.51, 'Eb6': 1244.51, 'E6': 1318.51, 'F6': 1396.91, 'F#6': 1479.98, 'Gb6': 1479.98, 'G6': 1567.98, 'G#6': 1661.22, 'Ab6': 1661.22, 'A6': 1760.0, 'A#6': 1864.66, 'Bb6': 1864.66, 'B6': 1975.53, 'C7': 2093.0, 'C#7': 2217.46, 'Db7': 2217.46, 'D7': 2349.32, 'D#7': 2489.02, 'Eb7': 2489.02, 'E7': 2637.02, 'F7': 2793.83, 'F#7': 2959.96, 'Gb7': 2959.96, 'G7': 3135.96, 'G#7': 3322.44, 'Ab7': 3322.44, 'A7': 3520.0, 'A#7': 3729.31, 'Bb7': 3729.31, 'B7': 3951.07, 'C8': 4186.01, 'C#8': 4434.92, 'Db8': 4434.92, 'D8': 4698.63, 'D#8': 4978.03, 'Eb8': 4978.03, 'E8': 5274.04, 'F8': 5587.65, 'F#8': 5919.91, 'Gb8': 5919.91, 'G8': 6271.93, 'G#8': 6644.88, 'Ab8': 6644.88, 'A8': 7040.0, 'A#8': 7458.62, 'Bb8': 7458.62, 'B8': 7902.13}

Şimdi indirdiğimiz kayıtlardan birisinin ilk notasının bir bölümünü inceleyelim. Bu kayıtta C5 ve B5 arasındaki notalar sırasıyla flüt ile icra edilmiş. İlk olarak sadece ilk 3 saniyelik kısmı kesip inceleyelim

ornekleme_fr = 44100
muzik_sinyali = ess.MonoLoader(filename='/content/enstruman/flute/flute.nonvib.ff.C5B5.aiff', sampleRate=ornekleme_fr)()

# Kaydın ilk 3 saniyelik bölümünü alalım, bu bölümde C5 notası icra edilmiş 
sure_saniye = 3
muzik_sinyali_3s = muzik_sinyali[:int(ornekleme_fr*sure_saniye)]

fig = plt.figure(figsize=(12,3))
plt.plot(muzik_sinyali_3s)
plt.title('Flüt sinyali örneği (C5 notası icrası)');
plt.xlabel('zaman endeksi (n)');
plt.ylabel('x[n]');
Audio(muzik_sinyali_3s, rate=ornekleme_fr)
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
/tmp/ipykernel_9575/3513546028.py in <module>
      1 ornekleme_fr = 44100
----> 2 muzik_sinyali = ess.MonoLoader(filename='/content/enstruman/flute/flute.nonvib.ff.C5B5.aiff', sampleRate=ornekleme_fr)()
      3 
      4 # Kaydın ilk 3 saniyelik bölümünü alalım, bu bölümde C5 notası icra edilmiş
      5 sure_saniye = 3

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/essentia/standard.py in __init__(self, **kwargs)
     42 
     43             # configure the algorithm
---> 44             self.configure(**kwargs)
     45 
     46         def configure(self, **kwargs):

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/essentia/standard.py in configure(self, **kwargs)
     62                 kwargs[name] = convertedVal
     63 
---> 64             self.__configure__(**kwargs)
     65 
     66         def compute(self, *args):

RuntimeError: Error while configuring MonoLoader: AudioLoader: Could not open file "/content/enstruman/flute/flute.nonvib.ff.C5B5.aiff", error = No such file or directory

Şekil 8.1: Tek nota icrası içeren ses sinyali dalga formu

Kaydı dinlediğinizde C5 notasını icrasını duyacaksınız.

Dalga formunu yakından görmek için sinyalin orta bölgesinden daha küçük bir kesitini inceleyelim. Değerleri orta bölgeden 500 örnek içerecek şekilde seçtik.

baslangic_endeks = 65000
bitis_endeks = 65500
muzik_sinyali_kesit = muzik_sinyali_3s[baslangic_endeks:bitis_endeks]
fig = plt.figure(figsize=(12,3))
plt.plot(muzik_sinyali_kesit)
plt.title('Flüt sinyali örneği (C5 notası icrası) - kesit');
plt.xlabel('zaman endeksi (n)');
plt.ylabel('x[n]');
Audio(muzik_sinyali_kesit, rate=ornekleme_fr)
../_images/08_EzgiAnalizi_FrekansKestirimi_16_1.png

Şekil 8.2: Tek nota icrası içeren ses sinyali dalga formu (yakınlaştırılmış)

Şekilde bir temel dalga formunun kendini tekrarladığını görebiliyoruz. Yaklaşık bir hesapla kendini tekrar eden ana dalga formunun frekansını hesaplayalım:

Bu 500 örnekli sinyalde toplamda yaklaşık 6 periyod gözlüyoruz. Örnek sayısı cinsinden periyodumuzu yaklaşık 500/6 örnek olarak alalım. 1 saniyede 44100 örnek bulunduğu için (dosyayı okurken örnekleme frekansımızı 44100 Hz olarak seçmiştik), bu durumda 1 saniyede toplam; 44100 / (500/6) adet dalga bulunduğunu, bu sayının saniyedeki tekrar sayısı olan temel titreşim frekansına karşılık geldiğini düşünebiliriz.

print("Gözleme dayalı hesaplanan temel titreşim frekansı:", 44100/(500/6), "Hz")
print("C5 notasının standart frekansı:", nota_frekanslari["C5"], "Hz")
Gözleme dayalı hesaplanan temel titreşim frekansı: 529.2 Hz
C5 notasının standart frekansı: 523.25 Hz

Gözlemle, dalga formunun uzunluğu (periyod) üzerinden, temel titreşim frekansı hesaplayabildik ve bunu icra edilen notanın frekansı ile karşılaştırarak doğrulayabildik. Peki her müzik sinyalinin analizi için bunu mu yapacağız? Elbette hayır, temel titreşim frekansını otomatik bulan fonksiyonlar yazıp kullanmayı tercih edeceğiz. Ezgi analizine başlamak için öncelikle her küçük kesit için otomatik kestirim yapabilmemiz lazım. Öncelikle bu küçük kesitteki temel titreşim frekans değişimini hazır bir kütüphane fonksiyonu yardımı ile kestirelim. 10 milisaniyede bir ölçüm yapalım ve ölçüm sonucumuzu spektrogram ile beraber çizdirelim.

pencere_kaydirma_miktari = int(0.01*ornekleme_fr) # pencere kaydırma miktarını 10 milisaniye olarak seçip örnek sayısı cinsinden ifade ettik
pencere_genisligi = pencere_kaydirma_miktari * 3 # pencere boyutumuzu da kaydırma miktarına orantılı belirleyelim: 3 kat

# Hazır fonksiyon ile temel titreşim frekansı kestirimi
f0, periyodiklik, periyodiklik_olasiligi = librosa.pyin(muzik_sinyali_3s, fmin=50, fmax=3000, sr=ornekleme_fr, win_length=pencere_genisligi, hop_length=pencere_kaydirma_miktari)

# periyodik olmayan bölgelerde kestirilen frekansları sıfırlayalım
f0[~periyodiklik] = 0

# Librosa ile spektrogram hesabı
fft_N = 4096
D = librosa.stft(muzik_sinyali_3s, n_fft=fft_N) 
S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)

# Spektrogram matrisinin çizdirilmesi
zamanEkseni = np.arange(S_db.shape[1]) * pencere_kaydirma_miktari / float(ornekleme_fr)
frekansEkseniHz = np.arange(S_db.shape[0]) * float(ornekleme_fr) / float(fft_N)


fig = plt.figure(figsize=(12,9))
plt.title('Flüt sinyali örneği (C5 notası icrası)');
plt.subplot(2,1,1)
plt.pcolormesh(zamanEkseni, frekansEkseniHz, S_db)
plt.ylim([0, 3000]) # spektrogramın 0-3000 Hz arası bölümünü görselleştirelim
plt.ylabel('frekans(Hz)');
plt.subplot(2,1,2)
plt.plot(f0)
plt.title('Librosa.pyin fonksiyonu ile kestirilen frekans değerleri');
plt.ylabel('f0 (Hz)');
plt.xlabel('pencere endeksi (n)');
../_images/08_EzgiAnalizi_FrekansKestirimi_21_0.png

Şekil 8.3: Spektrogram (yakınlaştırılmış) ve librosa.pyin fonksiyonu ile elde edilen frekans serisi

Spektrogramı, librosa.pyin fonksiyonu ile kestirilen değerler ile karşılaştırdığımızda gerçekten de kestirilen frekanslarda spektrogramda güçlü bileşenlerin bulunduğunu rahatlıkla görebiliyoruz. Peki librosa.pyin bu kestirim işlemini nasıl gerçekleştiriyor? PYIN algoritmasının detayları için ilgili makaleyi inceleyebilirsiniz. PYIN ve diğer birçok algoritma sinyalin kendi üzerinde kaydırılıp tekrar örtüşme noktalarının bulunması yöntemine dayanır. PYIN, sinyali kaydırılmış halinden çıkartıp farkın en düşük olduğu kaydırma miktarlarını bularak gerçekleştirir. Bu işlemin diğer alternatifi sinyali kaydırılmış haliyle çarpıp, çarpım sonucunun büyük olduğu kaydırma miktarlarını bulmaktır. Bu literatürde “otokorelasyon temelli frekans kestirimi” olarak anılır ve PYIN’in öncülü bir algoritmadır. Aşağıda bunu gerçekleştiren bir fonksiyon yazalım ve sinyalimiz üzerinde test edelim.

Bir \(x[n]\) sinyalinin otokorelasyonunu (İng:autocorrelation) , \(r_k\), aşağıdaki gibi tanımlayabiliriz:

\[ r_k = \sum_n x[n] x[n+k] \]

Bu, temelde sinyali sinyalin \(k\) örnek kaydırılmış haliyle skaler çarpımına karşılık gelir. Periyodik bir \(x[n]\) sinyali için farklı \(k\) değerleri ile \(r_k\)’yı hesaplayıp çizdirirsek periyodun tam katlarında sinyalin kaydırılmış hali kendiyle örtüşeceğinden skaler çarpımın yüksek olmasını bekleriz.

def otokorelasyon(x_n):
  otokor = np.zeros_like(x_n)
  for k in range(x_n.shape[0]):
    if k==0:
      x_n_kaydirilmis = x_n
    else:
      x_n_kaydirilmis = np.hstack((np.zeros(k),x_n[:-k]))
    otokor[k] = np.dot(x_n,x_n_kaydirilmis)
  return otokor

fig = plt.figure(figsize=(12,3))
otokor_x_n = otokorelasyon(muzik_sinyali_kesit)
plt.plot(otokor_x_n);
plt.title('Sinyal kesitinin otokorelasyonu');
plt.ylabel('r[n]');
plt.xlabel('zaman endeksi (n)');
../_images/08_EzgiAnalizi_FrekansKestirimi_25_0.png

Şekil 8.4: Otokorelasyon sinyali örneği

Otokorelasyon sinyalinde periyodun tam katlarında tepeler gözlüyoruz. Otokorelasyon sinyalinin, sinyalin orijinalinden temel farkı: 0(sıfır) endeksinin ilk periyodun başı olması ve böylece sonraki periyodun başını ilk tepenin yerini bularak kestirme imkanımızın bulunması. Otokorelasyon sinyalinde 50 ve 150 örnekler arasındaki tepenin konumunu bulduğumuzda periyod bilgisine erişmiş oluruz ve periyod bilgisinden de frekansı kestirebiliriz.

periyod = np.argmax(otokor_x_n[50:150]) + 50
frekans = ornekleme_fr / periyod
print("Otokorelasyon tepe noktası endeksi:", periyod)
print("Otokorelasyondan kestirilen frekans:", frekans, "Hz")
Otokorelasyon tepe noktası endeksi: 83
Otokorelasyondan kestirilen frekans: 531.3253012048193 Hz

Otokorelasyon sinyalinden frekans kestirimi yapabileceğimizi bir örnekle görmüş olduk. Otokorelasyon sinyalinde birçok tepe varolduğu için belirli bir aralıktaki tepeyi aramak işimizi kolaylaştıracaktır. Eğer maksimum periyod ve minimum periyod için bir öngörümüz var ise bu aralıktaki tepeyi aramamız yeterli olacaktır. Çoğunlukla örnek sayısı cinsinden periyod yerine minimum frekans ve maksimum frekans için öngörümüz vardır ve periyod bilgisini de frekans bilgisinden elde edebiliriz. Örneğin elimizdeki sinyal bir konuşma sinyali ise sinyalin frekansının 60 Hz-500 Hz arasında olduğunu varsayarak ilerleyebiliriz. Aşağıda sunulan, minimum ve maksimum frekans öngörüsünü kullanarak frekans kestirim yapan fonksiyonu inceleyiniz:

def frekans_kestir_otokorelasyon(x_n, ornekleme_fr, minF0, maxF0):
    '''Otokorelasyon yöntemi ile frekans kestirimi
    Parametreler
    ----------
    x_n : numpy.array
        Sinyal kesiti
    ornekleme_fr,minF0,maxF0 : float/int
        Örnekleme frekansı, minimum ve maksimum frekans değerleri
        
    Çıktı
    -------
    f0 : float
        Kestirien frekans (Hz) 
    ''' 
    f0 = 0
    # Ilk olarak limitleri frekanstan periyod bilgisine dönüştürelim
    minT0 = int(ornekleme_fr/maxF0)
    maxT0 = int(ornekleme_fr/minF0)
    # minT0-maxT0 arasındaki kaydırma değerlerinde otokorelasyonun 
    #  en yüksek değer aldığı kaydırma miktarını bulmayı hedefliyoruz 
    max_deger = -1;T0 = -1
    for k in range(minT0, maxT0):
        x_n_kaymis = np.hstack((np.zeros(k),x_n[:-k]))
        otokor_k = np.dot(x_n,x_n_kaymis)
        if otokor_k > max_deger:
            T0 = k
            max_deger = otokor_k
    # T0 değişkeni maksimum noktanın endeksini içeriyor, örnek sayısı cinsinden 
    # periyod bilgisine karşılık geliyor, frekansa dönüştürelim 
    f0 = float(ornekleme_fr) / T0
    return f0

Şimdi fonksiyonumuzu C5 notasını icra eden flüt kaydı üzerinde test edelim.

minF0 = 50 # Hz cinsinden beklenen en düşük frekans 
maxF0 = 2000 # Hz cinsinden beklenen en yüksek frekans
pencere_genisligi = 4096
pencere_kaydirma_miktari = 1024
w = get_window('blackman', pencere_genisligi)
baslangic_noktalari = np.arange(0, muzik_sinyali_3s.size - pencere_genisligi, pencere_kaydirma_miktari, dtype = int)
pencere_sayisi = baslangic_noktalari.size
# Sinyali kesit/pencerelere bölüp her bir pencere için frekans kestirimi yapalım
f0 = np.zeros_like(baslangic_noktalari,dtype=float) 
for k in range(pencere_sayisi):
    x_n = muzik_sinyali_3s[baslangic_noktalari[k] : baslangic_noktalari[k] + pencere_genisligi] * w # pencereleme işlemi
    f0[k] = frekans_kestir_otokorelasyon(x_n, ornekleme_fr, minF0, maxF0)
# Kestirilen frekans değerlerini çizdirelim
fig = plt.figure(figsize=(12,4))
plt.title('Flüt sinyali örneği (C5 notası icrası)');
plt.plot(f0)
plt.title('frekans_kestir_otokorelasyon() fonksiyonu ile kestirilen frekans değerleri');
plt.ylabel('f0 (Hz)');
plt.xlabel('pencere endeksi (n)');
../_images/08_EzgiAnalizi_FrekansKestirimi_34_0.png

Şekil 8.5: Otokorelasyon yöntemi ile elde edilen frekans serisi

Kaydın iki kenarında sessizlik var. O bölgelerdeki değerleri sıfırlayabiliriz. Bunu enerji parametresini kullanarak gerçekleştirelim. Enerjinin maksimum enerjinin %5’inin altına düştüğü pencereleri sessiz bölgeler olarak ele alacağız.

# Enerji hesabı 
NRG = np.zeros_like(f0,dtype = float)
for k in range(pencere_sayisi):
    x_n = muzik_sinyali_3s[baslangic_noktalari[k]:baslangic_noktalari[k] + pencere_genisligi] * w
    NRG[k] = np.sum(np.power(x_n,2))

# Enerjinin maksimumunun %5'i altındaki bölgelerde frekans değerlerinin sıfırlanması
esik_deger_oran = 0.05
f0[NRG < np.max(NRG)*esik_deger_oran] = 0

# Kestirilen frekans değerlerini çizdirelim
fig = plt.figure(figsize=(12,4))
plt.title('Flüt sinyali örneği (C5 notası icrası)');
plt.plot(f0)
plt.title('frekans_kestir_otokorelasyon() fonksiyonu ile kestirilen frekans değerleri');
plt.ylabel('f0 (Hz)');
plt.xlabel('pencere endeksi (n)');
../_images/08_EzgiAnalizi_FrekansKestirimi_37_0.png

Şekil 8.6: Otokorelasyon yöntemi ile elde edilen frekans serisi (sinyalin enerjisi düşük bölgelerinde frekans sıfırlandıktan sonra)

Şimdi kendi yazdığımız fonksiyon ve librosa.pyin fonksiyonu ile sinyalin tümü üzerinde frekans kestirimi yapıp sonuçları karşılaştıralım.

pencere_kaydirma_miktari = int(0.01*ornekleme_fr) # pencere kaydırma miktarını 10 milisaniye olarak seçip örnek sayısı cinsinden ifade ettik
pencere_genisligi = pencere_kaydirma_miktari * 3 # pencere boyutumuzu da kaydırma miktarına orantılı belirleyelim: 3 kat
minF0 = 50 # minimum frekans
maxF0 = 2000 # maximum frekans

# Librosa.pyin hazır fonksiyonu ile temel titreşim frekansı kestirimi
f0_pyin, periyodiklik, periyodiklik_olasiligi = librosa.pyin(muzik_sinyali, fmin=minF0, fmax=maxF0, sr=ornekleme_fr, win_length=pencere_genisligi, hop_length=pencere_kaydirma_miktari)

# periyodik olmayan bölgelerde kestirilen frekansları sıfırlayalım
f0_pyin[~periyodiklik] = 0

# Otokorelasyon temelli frekans kestirimi
w = get_window('blackman', pencere_genisligi)
baslangic_noktalari = np.arange(0, muzik_sinyali.size - pencere_genisligi, pencere_kaydirma_miktari, dtype = int)
pencere_sayisi = baslangic_noktalari.size
f0_otokor = np.zeros_like(baslangic_noktalari,dtype=float)
NRG = np.zeros_like(f0_otokor, dtype = float)
for k in range(pencere_sayisi): 
    x_n = muzik_sinyali[baslangic_noktalari[k] : baslangic_noktalari[k] + pencere_genisligi] * w # pencereleme işlemi
    f0_otokor[k] = frekans_kestir_otokorelasyon(x_n, ornekleme_fr, minF0, maxF0)
    NRG[k] = np.sum(np.power(x_n,2))

esik_deger_oran = 0.05
f0_otokor[NRG < np.max(NRG)*esik_deger_oran] = 0

# Ölçülen frekans değerlerinin üstüste çizdirilmesi
fig = plt.figure(figsize=(12,4))
plt.title('Flüt sinyali örneği');
plt.plot(f0_pyin,'b', label='PYIN kestirim sonucu')
plt.plot(f0_otokor,'k', label='Otokorelasyon ile kestirim sonucu')
plt.title('frekans_kestir_otokorelasyon() fonksiyonu ile kestirilen frekans değerleri');
plt.ylabel('f0 (Hz)');
plt.xlabel('pencere endeksi (n)');
plt.legend();
../_images/08_EzgiAnalizi_FrekansKestirimi_40_0.png

Şekil 8.7: Librosa.pyin ve otokorelasyon yöntemi ile elde edilen frekans serileri

Sonuçların birçok noktada örtüştüğünü, pyin’in sondan önceki notada frekansı yarısı değerinde kestirdiğini (oktav hatası yaptığını) görebiliyoruz. Oktav hatası (frekansı doğru değerinden *\(2^k\) farklı olarak tespit etmek, \(k\) tamsayı) yapılan en tipik hatalardandır.

8.1.1. Frekans kestirim algoritmasının test edilmesi#

Peki, yapılan frekans kestirim sonucunun doğruluğunu nasıl ölçebiliriz? Elimizdeki veriler sentetik değilse gerçek frekans değerinden emin olmamız mümkün değildir. Ancak başka özniteliklerle (örneğin spektrogram ile) karşılaştırmak veya elle işaretlemesi yapılmış veriler üzerinden standart testler gerçekleştirmek şeklinde opsiyonlarımız bulunmakta. Bu örnek için spektrogram ile beraber çizdirerek karşılaştırmaya bir örnek görelim.

8.1.1.1. Başka özniteliklerle (örneğin spektrogram ile) karşılaştırmak#

fft_N = 4096
spktrgrm = np.array([]).reshape(0, int(fft_N / 2)) # spektogram boş bir dizi olarak başlatılıyor
for k in range(pencere_sayisi):
    pencere_baslangici = baslangic_noktalari[k]
    x_w = muzik_sinyali[pencere_baslangici:pencere_baslangici + pencere_genisligi] * w
    X = np.fft.fft(x_w, fft_N)
    genlikX = np.abs(X[:int(fft_N / 2)])
    genlikX[genlikX < np.finfo(float).eps] = np.finfo(float).eps # log operasyonundan önce önlem
    genlik_spektrumu = 20 * np.log10(genlikX)
    spktrgrm = np.vstack((spktrgrm, genlik_spektrumu)) # k. sinyal kesitinin spektrumunun eklenmesi

# Spektrogram matrisinin çizdirilmesi
zamanEkseni = np.arange(spktrgrm.shape[0]) * pencere_kaydirma_miktari / float(ornekleme_fr)
frekansEkseniHz = np.arange(spktrgrm.shape[1]) * float(ornekleme_fr) / float(fft_N)

fig = plt.figure(figsize=(15,4))
plt.pcolormesh(zamanEkseni, frekansEkseniHz, np.transpose(spktrgrm))
plt.plot(zamanEkseni, f0_otokor,'r', label='Otokorelasyon ile kestirim sonucu')
plt.ylim([0, 3000]) # spektrogramın 0-3000 Hz arası bölümünü görselleştirelim
plt.title('Genlik Spektrogramı (dB)')
plt.ylabel('frekans(Hz)')
plt.xlabel('zaman(saniye)')
plt.show()
../_images/08_EzgiAnalizi_FrekansKestirimi_46_0.png

Şekil 8.8: Spektrogram ve otokorelasyon yöntemi ile elde edilen frekans serisi

Spektrogramla kestirilen frekans değerlerini üstüste çizdirdiğimizde temel titreşim frekans değerlerinin spektrogramda 1. harmonik izi ile örtüştüğünü doğrulayabiliyoruz. 15. saniye civarında yanlış kestirim yapılmış pencereler olduğu da görülebiliyor.

8.1.1.2. Çeşitli yöntemlerin standart metrikler kullanılarak test edilmesi#

Birden fazla algoritmanın kestirdiği temel titreşim frekans değerlerini, hangisinin daha iyi sonuç verdiğini anlamak için, karşılaştırmamız gerekirse bu alanda kullanılan standart test yöntemlerini kullanmamızda fayda var. Aşağıda güncel olarak kullanılan, başarım seviyesi yüksek 3 ayrı kütüphanenin standart veri ve test yöntemi ile karşılaştırılmasına yönelik kod örneği bulabilirsiniz.

Burada şu noktaları gözden kaçırmamak gerekir:

  • Her bir algoritma için bir dizi parametre bulunmaktadır ve parametre seçimi başarı düzeyini etkileyecektir. Çeşitli parametre kombinasyonları ile testler yapıp en iyi kombinasyonları elde ettikten sonra karşılaştırma yapmak daha güvenilir bir karşılaştırma sonucu sunacaktır

  • Algoritmaların farklı ses örneklerinde başarı düzeyleri farklı olacaktır. Tekil örneklerden yola çıkarak sonuca varmak yerine çok sayıda ve farklı türde veriler üzerinde test yapıp raporlamak faydalı olacaktır

  • Frekans kestirim sonuçlarındaki bazı hatalar (örneğin oktav hataları) kural tabanlı işlemlerle düzeltilebilmektedir. Uygulama geliştirirken bu olasılık da değerlendirilmelidir.

Testlerimizde verilere erişim ve test fonksiyonları için şu kütüphaneleri kullanacağız:

  • mirdata: müzik bilgi erişim alanında kullanılan bazı veri kümelerinin kolayca yüklenmesini sağlar

  • mir_eval: müzik bilgi erişim alanında standart kalite testleri gerçekleştirmek için kullanılan bir kütüphane

Ayrıca frekans kestirim için güncel tekniklerden birisi olan Crepe algoritmasını da testlerimize dahil edeceğiz.

!pip install crepe mir_eval mirdata
import mirdata, mir_eval, crepe
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting crepe
  Downloading crepe-0.0.12.tar.gz (15 kB)
Collecting mir_eval
  Downloading mir_eval-0.7.tar.gz (90 kB)
     |████████████████████████████████| 90 kB 5.2 MB/s 
?25hCollecting mirdata
  Downloading mirdata-0.3.6-py3-none-any.whl (13.1 MB)
     |████████████████████████████████| 13.1 MB 41.0 MB/s 
?25hRequirement already satisfied: numpy>=1.14.0 in /usr/local/lib/python3.7/dist-packages (from crepe) (1.21.6)
Requirement already satisfied: scipy>=1.0.0 in /usr/local/lib/python3.7/dist-packages (from crepe) (1.7.3)
Requirement already satisfied: matplotlib>=2.1.0 in /usr/local/lib/python3.7/dist-packages (from crepe) (3.2.2)
Collecting resampy<0.3.0,>=0.2.0
  Downloading resampy-0.2.2.tar.gz (323 kB)
     |████████████████████████████████| 323 kB 49.7 MB/s 
?25hRequirement already satisfied: h5py in /usr/local/lib/python3.7/dist-packages (from crepe) (3.1.0)
Collecting hmmlearn<0.3.0,>=0.2.0
  Downloading hmmlearn-0.2.7-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (129 kB)
     |████████████████████████████████| 129 kB 43.1 MB/s 
?25hRequirement already satisfied: imageio>=2.3.0 in /usr/local/lib/python3.7/dist-packages (from crepe) (2.9.0)
Requirement already satisfied: scikit-learn>=0.16 in /usr/local/lib/python3.7/dist-packages (from crepe) (1.0.2)
Requirement already satisfied: pillow in /usr/local/lib/python3.7/dist-packages (from imageio>=2.3.0->crepe) (7.1.2)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=2.1.0->crepe) (3.0.9)
Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=2.1.0->crepe) (2.8.2)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=2.1.0->crepe) (0.11.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=2.1.0->crepe) (1.4.4)
Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from kiwisolver>=1.0.1->matplotlib>=2.1.0->crepe) (4.1.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.1->matplotlib>=2.1.0->crepe) (1.15.0)
Requirement already satisfied: numba>=0.32 in /usr/local/lib/python3.7/dist-packages (from resampy<0.3.0,>=0.2.0->crepe) (0.56.0)
Requirement already satisfied: llvmlite<0.40,>=0.39.0dev0 in /usr/local/lib/python3.7/dist-packages (from numba>=0.32->resampy<0.3.0,>=0.2.0->crepe) (0.39.0)
Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.7/dist-packages (from numba>=0.32->resampy<0.3.0,>=0.2.0->crepe) (4.12.0)
Requirement already satisfied: setuptools in /usr/local/lib/python3.7/dist-packages (from numba>=0.32->resampy<0.3.0,>=0.2.0->crepe) (57.4.0)
Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.16->crepe) (1.1.0)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.16->crepe) (3.1.0)
Requirement already satisfied: future in /usr/local/lib/python3.7/dist-packages (from mir_eval) (0.16.0)
Requirement already satisfied: librosa>=0.8.0 in /usr/local/lib/python3.7/dist-packages (from mirdata) (0.8.1)
Collecting pretty-midi>=0.2.8
  Downloading pretty_midi-0.2.9.tar.gz (5.6 MB)
     |████████████████████████████████| 5.6 MB 48.3 MB/s 
?25hRequirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from mirdata) (2.23.0)
Collecting jams
  Downloading jams-0.3.4.tar.gz (51 kB)
     |████████████████████████████████| 51 kB 85 kB/s 
?25hRequirement already satisfied: tqdm in /usr/local/lib/python3.7/dist-packages (from mirdata) (4.64.0)
Requirement already satisfied: smart-open>=5.0.0 in /usr/local/lib/python3.7/dist-packages (from mirdata) (5.2.1)
Requirement already satisfied: chardet in /usr/local/lib/python3.7/dist-packages (from mirdata) (3.0.4)
Requirement already satisfied: pyyaml in /usr/local/lib/python3.7/dist-packages (from mirdata) (6.0)
Collecting Deprecated>=1.2.13
  Downloading Deprecated-1.2.13-py2.py3-none-any.whl (9.6 kB)
Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.7/dist-packages (from Deprecated>=1.2.13->mirdata) (1.14.1)
Requirement already satisfied: cached-property in /usr/local/lib/python3.7/dist-packages (from h5py->crepe) (1.5.2)
Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.7/dist-packages (from librosa>=0.8.0->mirdata) (21.3)
Requirement already satisfied: pooch>=1.0 in /usr/local/lib/python3.7/dist-packages (from librosa>=0.8.0->mirdata) (1.6.0)
Requirement already satisfied: decorator>=3.0.0 in /usr/local/lib/python3.7/dist-packages (from librosa>=0.8.0->mirdata) (4.4.2)
Requirement already satisfied: audioread>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from librosa>=0.8.0->mirdata) (3.0.0)
Requirement already satisfied: soundfile>=0.10.2 in /usr/local/lib/python3.7/dist-packages (from librosa>=0.8.0->mirdata) (0.10.3.post1)
Requirement already satisfied: appdirs>=1.3.0 in /usr/local/lib/python3.7/dist-packages (from pooch>=1.0->librosa>=0.8.0->mirdata) (1.4.4)
Collecting mido>=1.1.16
  Downloading mido-1.2.10-py2.py3-none-any.whl (51 kB)
     |████████████████████████████████| 51 kB 8.5 MB/s 
?25hRequirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->mirdata) (2022.6.15)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->mirdata) (2.10)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->mirdata) (1.24.3)
Requirement already satisfied: cffi>=1.0 in /usr/local/lib/python3.7/dist-packages (from soundfile>=0.10.2->librosa>=0.8.0->mirdata) (1.15.1)
Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.0->soundfile>=0.10.2->librosa>=0.8.0->mirdata) (2.21)
Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->numba>=0.32->resampy<0.3.0,>=0.2.0->crepe) (3.8.1)
Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from jams->mirdata) (1.3.5)
Requirement already satisfied: sortedcontainers>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from jams->mirdata) (2.4.0)
Requirement already satisfied: jsonschema>=3.0.0 in /usr/local/lib/python3.7/dist-packages (from jams->mirdata) (4.3.3)
Requirement already satisfied: importlib-resources>=1.4.0 in /usr/local/lib/python3.7/dist-packages (from jsonschema>=3.0.0->jams->mirdata) (5.9.0)
Requirement already satisfied: pyrsistent!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0 in /usr/local/lib/python3.7/dist-packages (from jsonschema>=3.0.0->jams->mirdata) (0.18.1)
Requirement already satisfied: attrs>=17.4.0 in /usr/local/lib/python3.7/dist-packages (from jsonschema>=3.0.0->jams->mirdata) (22.1.0)
Requirement already satisfied: pytz>=2017.3 in /usr/local/lib/python3.7/dist-packages (from pandas->jams->mirdata) (2022.2.1)
Building wheels for collected packages: crepe, resampy, mir-eval, pretty-midi, jams
  Building wheel for crepe (setup.py) ... ?25l?25hdone
  Created wheel for crepe: filename=crepe-0.0.12-py3-none-any.whl size=134848696 sha256=dd0771d7f13d72d7133e4f813a5e4957ea767e5918b5412138f5646871eab606
  Stored in directory: /root/.cache/pip/wheels/56/05/32/fccf64e8ae720b34b486b6a8a08712777fd0beab419980fea3
  Building wheel for resampy (setup.py) ... ?25l?25hdone
  Created wheel for resampy: filename=resampy-0.2.2-py3-none-any.whl size=320732 sha256=b9ce4d365b1facbd44c7909aba6541bd219c4c0dc4c4ed2fcc8161533eddc21b
  Stored in directory: /root/.cache/pip/wheels/a0/18/0a/8ad18a597d8333a142c9789338a96a6208f1198d290ece356c
  Building wheel for mir-eval (setup.py) ... ?25l?25hdone
  Created wheel for mir-eval: filename=mir_eval-0.7-py3-none-any.whl size=100721 sha256=b128be6f46da0f9588f89b32948ab0be8b22a611bc344426b26b50dd3a193d68
  Stored in directory: /root/.cache/pip/wheels/18/5a/46/d2527ff1fd975e1a793375e6ed763bfe4d3ea396b7cdc470eb
  Building wheel for pretty-midi (setup.py) ... ?25l?25hdone
  Created wheel for pretty-midi: filename=pretty_midi-0.2.9-py3-none-any.whl size=5591955 sha256=40ec4aad7f839d33f2cee924dd5b908a6d7f47edfe94fa88a1345e62fe0b56ca
  Stored in directory: /root/.cache/pip/wheels/ad/74/7c/a06473ca8dcb63efb98c1e67667ce39d52100f837835ea18fa
  Building wheel for jams (setup.py) ... ?25l?25hdone
  Created wheel for jams: filename=jams-0.3.4-py3-none-any.whl size=64922 sha256=99c48004aecebb2b709360ab6e43f1f4992115529e437cc5488e71ff7ffdf3a2
  Stored in directory: /root/.cache/pip/wheels/c9/aa/16/ce72bc4caa58dfab819e3f46b3542f2bf90a83009f4ea07a48
Successfully built crepe resampy mir-eval pretty-midi jams
Installing collected packages: resampy, mir-eval, mido, pretty-midi, jams, hmmlearn, Deprecated, mirdata, crepe
  Attempting uninstall: resampy
    Found existing installation: resampy 0.4.0
    Uninstalling resampy-0.4.0:
      Successfully uninstalled resampy-0.4.0
Successfully installed Deprecated-1.2.13 crepe-0.0.12 hmmlearn-0.2.7 jams-0.3.4 mido-1.2.10 mir-eval-0.7 mirdata-0.3.6 pretty-midi-0.2.9 resampy-0.2.2

mirdata aracılığıyla erişilebilen veri kümelerinin listesi için bakınız. Listelenen veriler içerisinde F0 etiketleri içerenlerden birisi olan “vocadito”yu kullanacağız.

# Verilerin indirilmesi
vocadito = mirdata.initialize('vocadito')
vocadito.download()
# İndirilen verilerdeki dosya bilgilerinin yüklenmesi
dosyalar = vocadito.load_tracks()
55.8MB [01:56, 501kB/s]                            
# Analiz parametrelerinin belirlenmesi 
ornekleme_fr = 44100
pencere_kaydirma_miktari = int(0.005*ornekleme_fr)
pencere_genisligi = pencere_kaydirma_miktari * 6
minF0 = 50
maxF0 = 500 # vokal örneği üzerinde kestirim yapacağımız için üst limiti düşük tutabiliriz

Aşağıdaki kod bir ses dosyası için üç algoritmanın frekans kestirim sonuçlarının karşılaştırmasını gerçekleştirir. Çalıştırıp sonucu aldıktan sonra isterseniz ilk satırı değiştirip analizi tüm veriler üzerinde işletebilirsiniz.

# for dosya_ismi in dosyalar.keys(): # veri kümesindeki tüm dosyalar için test yapmak isterseniz bir alttaki yerine bu satırı kullanabilirsiniz
for dosya_ismi in ['1']: # listeye isterseniz başka örneklerin endekslerini (virgülle ayırarak) ekleyebilirsiniz
  dosya = dosyalar[dosya_ismi]
  print('Ses dosyası konumu:', dosya.audio_path)
  print('Etiket dosyası konumu:', dosya.f0_path)
  # Ses dosyasının okunması
  x = ess.MonoLoader(filename = dosya.audio_path, sampleRate = ornekleme_fr)()
  
  # Melodia ile temel titreşim frekans kestirimi
  frekans_serisi_melodia, guvenilirlik = ess.PitchMelodia(guessUnvoiced = True, 
                                                  frameSize = int(pencere_genisligi), 
                                                  hopSize = pencere_kaydirma_miktari,
                                                  maxFrequency = maxF0,
                                                  sampleRate=ornekleme_fr)(x)
  # Güvenilirliğin düşük olduğu kestirimleri sıfırlayalım
  frekans_serisi_melodia[guvenilirlik <= 0.0] = 0
  zaman_serisi_melodia = np.linspace(0, x.size/ornekleme_fr, num=frekans_serisi_melodia.size, endpoint=False) 

  # Crepe ile temel titreşim frekans kestirimi
  print('Crepe frekans kestirim işlemi gerçekleştiriliyor')
  zaman_serisi_crepe, frekans_serisi_crepe, guvenilirlik, activation = crepe.predict(x, ornekleme_fr, viterbi=True)
  # Güvenilirliğin düşük olduğu kestirimleri sıfırlayalım
  frekans_serisi_crepe[guvenilirlik < 0.5] = 0

  # Librosa.pyin ile temel titreşim frekans kestirimi
  print('Pyin frekans kestirim işlemi gerçekleştiriliyor')
  frekans_serisi_pyin, periyodiklik, periyodiklik_olasiligi = librosa.pyin(x, fmin=minF0, fmax=maxF0, sr=ornekleme_fr, win_length=pencere_genisligi, hop_length=pencere_kaydirma_miktari)
  zaman_serisi_pyin = np.linspace(0, x.size/ornekleme_fr, num=frekans_serisi_pyin.size, endpoint=False) 

  # Veri etiketlerinin (doğru değerlerin) okunması
  zaman_serisi_referans, frekans_serisi_referans = mir_eval.io.load_time_series(dosya.f0_path, delimiter=',')

  # Gerçek değerleri ile kestirilen değerlerin karşılaştırılması 
  sonuclar_melodia = mir_eval.melody.evaluate(zaman_serisi_referans, frekans_serisi_referans, zaman_serisi_melodia, frekans_serisi_melodia)
  sonuclar_crepe = mir_eval.melody.evaluate(zaman_serisi_referans, frekans_serisi_referans, zaman_serisi_crepe, frekans_serisi_crepe)
  sonuclar_pyin = mir_eval.melody.evaluate(zaman_serisi_referans, frekans_serisi_referans, zaman_serisi_pyin, frekans_serisi_pyin)
  
  for sonuclar, algoritma in zip([sonuclar_melodia, sonuclar_crepe, sonuclar_pyin],['melodia', 'crepe', 'pyin']):
    print('----------------------')
    print(algoritma, 'algoritması için sonuçlar:')
    for anahtar, deger in sonuclar.items():
      print(anahtar, deger)
Ses dosyası konumu: /root/mir_datasets/vocadito/Audio/vocadito_1.wav
Etiket dosyası konumu: /root/mir_datasets/vocadito/Annotations/F0/vocadito_1_f0.csv
Crepe frekans kestirim işlemi gerçekleştiriliyor
104/104 [==============================] - 161s 2s/step
Pyin frekans kestirim işlemi gerçekleştiriliyor
----------------------
melodia algoritması için sonuçlar:
Voicing Recall 0.899505766062603
Voicing False Alarm 0.26009615384615387
Raw Pitch Accuracy 0.8833058758923669
Raw Chroma Accuracy 0.8833058758923669
Overall Accuracy 0.8311779098217407
----------------------
crepe algoritması için sonuçlar:
Voicing Recall 0.9879187259747392
Voicing False Alarm 0.08798076923076924
Raw Pitch Accuracy 0.9829763866007688
Raw Chroma Accuracy 0.9829763866007688
Overall Accuracy 0.9571828032156589
----------------------
pyin algoritması için sonuçlar:
Voicing Recall 0.9925864909390445
Voicing False Alarm 0.14951923076923077
Raw Pitch Accuracy 0.9827018121911038
Raw Chroma Accuracy 0.9827018121911038
Overall Accuracy 0.9346382383781895

Testlerde kullanılan başarı ölçütlerine dair açıklamalar için bakınız:

Bu örnekte Crepe algoritmasının başarı düzeyinin diğer algoritmalardan yüksek olduğunu görebiliyoruz.

Son olarak frekans serilerini görsel olarak karşılaştırabiliriz. Değerler birbirine çok yakın olduğu için serileri y ekseninde kaydırarak çizdirmeyi tercih edeceğiz.

# Ölçülen frekans değerlerinin üstüste çizdirilmesi
kaydirma_miktari = 5
fig = plt.figure(figsize=(15,6))
plt.title('vocadito veri kümesi ilk dosyası için frekans kestirim sonuçları');
plt.plot(zaman_serisi_referans, frekans_serisi_referans, 'k', label='Etiket değerleri')
plt.plot(zaman_serisi_melodia, frekans_serisi_melodia+kaydirma_miktari, 'b', label='Melodia(+{})'.format(kaydirma_miktari))
plt.plot(zaman_serisi_crepe, frekans_serisi_crepe+2*kaydirma_miktari, 'r', label='Crepe(+{})'.format(2*kaydirma_miktari))
plt.plot(zaman_serisi_pyin, frekans_serisi_pyin+3*kaydirma_miktari, 'g', label='Pyin(+{})'.format(3*kaydirma_miktari))
plt.ylabel('f0 (Hz)');
plt.xlabel('pencere endeksi (n)');
plt.ylim(0, 300)
plt.legend();
../_images/08_EzgiAnalizi_FrekansKestirimi_61_0.png

Şekil 8.9: Vocadito veri kümesi ilk örneği için Melodia, Crepe ve Librosa.pyin ile elde edilen frekans serileri ve etiket değerleri

Bu örnek için, test ettiğimiz üç algoritma ile de gerçek etiket değerlerine çok yakın değerler elde ettiğimizi grafikten görebiliyoruz. En açık hatalar: Melodia bazı sessiz bölgelerde frekans değerleri döndürmüş. Muhtemelen bu hatalar yukarıda yaptığımız gibi enerjisi düşük bölgelerde frekans sıfırlanarak düzeltilebilir. Vokal kayıtlarının çoğu için benzer sonuçları bekleyebiliriz.

Bu defterimizde verili bir ses kaydında icra edilen frekans serisini kestirme konusunu ele aldık. Hem kendimiz bir fonksiyon yazdık, hem de güncel hazır araçların kullanımını örnekledik. Sonraki defterde frekans serisinin işlenmesine yönelik örnekler göreceğiz.

Yazar: Barış Bozkurt, editör: Ahmet Uysal