10. Ritmik Analiz#

Open In Colab

10.1. Ritmik Analiz#

Buraya kadar olan defterlerimizde temelde spektrum ve ezgi boyutlarını ele aldık. Bu defterimizde diğer önemli boyut olan ritim boyutunu ele alıyoruz.

Müzik ses sinyallerinin ritim boyutu ile ilişkili kavramların birçoğu oldukça tartışmaya açık ve kültüre özgüdürler. Burada amacımız sayısal sinyal işleme uygulamaları olduğu için mümkün olduğunca müzikolojik tartışmaların dışında kalmaya, basit sinyal öğeleri tanımlayıp kullanmaya çalışacağız. Bu defterde kullanacağımız temel kavramlar şunlar olacak:

  • Başlangıç anı (İng: onset) bir kayıttaki müzik notasının veya ses öğesinin başlangıç anına karşılık gelmektedir (bakınız: Bello et al 2005).

  • Vuruş (anları) (İng: beat): Vuruşu, müzikteki zaman organizasyonunda düzenli tekrarlayan, altta yatan temel zaman ızgarasının tik-tak’larından her biri gibi düşünebiliriz.

  • Tempo: Dakikadaki vuruş sayısı

Şimdi sırasıyla bu öğeleri sinyaller üzerinde gözleyip otomatik olarak tespit etmek için kullanılan algoritmaları ele almaya başlayalım. Önce kütüphanelerimizi kurup yükleyelim ve örnek kayıtlar indirelim.

!pip install essentia
import os, sys
import numpy as np
import matplotlib.pyplot as plt
import urllib.request
import librosa
from scipy.signal import get_window
import essentia.standard as ess
from essentia import Pool, array
from IPython.display import Audio
Requirement already satisfied: essentia in /home/baris/miniconda3/envs/KitapYazim/lib/python3.9/site-packages (2.1b6.dev857)
Requirement already satisfied: numpy>=1.8.2 in /home/baris/miniconda3/envs/KitapYazim/lib/python3.9/site-packages (from essentia) (1.22.4)
Requirement already satisfied: pyyaml in /home/baris/miniconda3/envs/KitapYazim/lib/python3.9/site-packages (from essentia) (6.0)
Requirement already satisfied: six in /home/baris/miniconda3/envs/KitapYazim/lib/python3.9/site-packages (from essentia) (1.16.0)
[   INFO   ] MusicExtractorSVM: no classifier models were configured by default

ODB verikümesinden bir grup örnek indirelim.

wav_klasor_linki = 'https://grfia.dlsi.ua.es/cm/worklines/pertusa/onset/ODB/sounds/'
etiket_klasor_linki = 'https://grfia.dlsi.ua.es/cm/worklines/pertusa/onset/ODB/ground-truth/'
ses_dosyalari = ['25-rujero.wav', 'RM-C036.wav', '3-you_think_too_muchb.wav', 'tiersen11.wav']

for dosya in ses_dosyalari:
    urllib.request.urlretrieve(wav_klasor_linki + dosya, dosya)
    etiket_dosyasi = dosya.replace('.wav','.txt')
    urllib.request.urlretrieve(etiket_klasor_linki + etiket_dosyasi, etiket_dosyasi)

print('Veriler indirildi')
Veriler indirildi

10.1.1. Başlangıç an(lar)ı tespiti#

Bir kaydın içerisindeki ses öğelerinin (notaların) başlangıç anlarının tespitinde ses öğesinin karakteri önem taşımaktadır. Genel olarak ses öğelerini şu 4 ayrı kategoriden birinde olarak düşünürüz:

  • Periyodik ve enerjisi ani değişen (İng: percussive), örnek: gitar veya piyano sesi

  • Periyodik ve enerjisi ani değişmeyen, örnek: ney sesi

  • Periyodik olmayan ve enerjisi ani değişen, örnek: davul sesi

  • Periyodik olmayan ve enerjisi ani değişmeyen, örnek: didjeridu sesi, elektronik müzikte kullanılan gürültünün filtrelenmesi ile elde edilen sesler

Her bir kategorideki ses için başlangıç anı tespit algoritmalarının etkinliği farklı olmaktadır.

Enerjisi ani değişen seslerin başlangıç anlarının tespiti için enerji sinyalini hesaplayıp kullanabileceğimizi düşündünüz sanırım. Enerji değişimini (enerjinin türevini) hesaplayan bir fonksiyon tanımlayalım ve indirdiğimiz sinyallerden birisiyle beraber görselleştirelim. Ayrıca, bu veritabanında elle işaretlenmiş başlangıç anları bilgisi de mevcut. Onları da beraber çizdirmek faydalı olacaktır.

def enerji_degisimi(x, pencere_uzunlugu, pencere_kaydirma_miktari, pencere_turu = "hann"):
  '''Verilen ses sinyalini pencerelere ayırıp her pencere için enerji hesaplayarak elde 
  edilen enerji serisinin türevini (yarım dalga doğrultma uygulandıktan sonra) döndürür.'''
  w = ess.Windowing(type = pencere_turu) # pencere ile çarpma işlemi gerçekleştirecek fonksiyon tanımı
  energy = ess.Energy() # enerji hesabı yapacak fonksiyon tanımı (6. defterde açık yazımı mevcut)
  NRG = [] # her pencere için hesaplanan enerjiyi bu listeye ekleyeceğiz
  for kesit in ess.FrameGenerator(x, frameSize = pencere_uzunlugu, hopSize = pencere_kaydirma_miktari):
    # sinyal kesitinin enerjisini hesaplayıp (yüksek değerlerin alçaklara yaklaştırılması amaçlı) log-kompresyon uyguluyoruz
    log_NRG = np.log10(energy(w(kesit))+np.finfo(float).eps)
    NRG.append(log_NRG)
  # veri türünü listeden diziye dönüştürelim
  NRG = np.array(NRG)
  # Fark/türev alma işlemi
  NRG_degisim = np.diff(NRG) 
  # Değişimin sadece pozitif kısımlarını alalım: yarım-dalga doğrultma (İng: half-wave rectification)
  NRG_degisim = (NRG_degisim + np.abs(NRG_degisim)) / 2
  # Genlik normalizasyonu
  NRG_degisim = NRG_degisim / max(NRG_degisim)
  
  return NRG_degisim

Analiz parametrelerimizi belirleyelim. Görsel incelemeyi kolaylaştırmak için sinyalin ilk 5 saniyelik kısmını kullanacağız.

ornekleme_fr = 44100
sure_saniye = 5 # saniye cinsinden ses sinyali uzunlugu
t = np.arange(sure_saniye * ornekleme_fr) / float(ornekleme_fr) # endekslere karşılık gelen saniye cinsinden zamanı taşıyan seri
pencere_uzunlugu = 2048 # ornek sayısı cinsinden pencere uzunluğu
pencere_kaydirma_miktari = 512 # ornek sayısı cinsinden pencere kaydırma miktarı

Ses sinyalini ve etiket dosyasını okutalım

dosya = '25-rujero.wav'
# Ses sinyalinin dosyadan okunması ve genlik normalizayonu
x = ess.MonoLoader(filename = dosya, sampleRate = ornekleme_fr)()
x = x[:sure_saniye * ornekleme_fr] # sinyalin ilk 5 saniyesini kullanacağız
x = x / np.max(np.abs(x))

# Elle işaretlenmiş başlangıç anlarını dosyadan okuyalım
baslangic_anlari = np.loadtxt(dosya.replace('.wav','.txt'))
baslangic_anlari = baslangic_anlari[baslangic_anlari < sure_saniye] # ilk 5 saniye içerisindeki başlangıç anlarını alalım

Ses kaydını, alttaki hücrenin çıktısının çal butonunu kullanarak dinleyiniz.

Audio(x, rate=ornekleme_fr)

Şimdi sinyal dalga formunu, enerji değişim sinyalini ve elle işaretlenmiş başlangıç noktalarını beraber çizdirebiliriz.

NRG_degisim = enerji_degisimi(x, pencere_uzunlugu, pencere_kaydirma_miktari)

f, axarr = plt.subplots(2, 1, figsize = (13, 4));
axarr[0].plot(t, x); axarr[0].set_title(dosya); axarr[0].axis('off');
axarr[0].vlines(baslangic_anlari, -1, 1, color='r');
axarr[1].plot(NRG_degisim,label = '(Log) Enerji değişimi)'); axarr[1].axis('off');axarr[1].legend(loc = 1);
../_images/10_RitmikAnaliz_17_0.png

Şekil 10.1: Ses kaydı, elle işaretlenmiş başlangıç anları (kırmızı dikey çizgiler) ve sinyalden hesaplanan enerji değişim fonksiyonu

Elle yerleştirilmiş başlangıç noktalarının enerji değişim sinyali tepeleriyle büyük oranda örtüştüğünü görebiliyoruz. Enerji değişim sinyalinin tepeleri üzerinden otomatik başlangıç noktası tespiti yapan bir fonksiyon yazmanız gerektiğini düşünün. Görece düşük genlikli bazı tepelerin gözardı edilmesi, bazılarının ise dikkate alınması gerekecektir. Bu tür durumda ilkin akla eşik değeri kullanmak (belirli genliğin altında olan tepeleri gözardı etmek) gelir. Buradaki örneği detaylı incelerseniz sabit bir eşik değeri belirlemenin zor olduğunu göreceksiniz. Bu tür durumlarda sinyalin özelliğine göre dinamik değişen lokal eşik değeri kullanabilirsiniz (Örneğin enerji değişim sinyalinin kesiti içerisindeki ortalama değerle orantılı bir eşik değeri kullanılabilir). Bu da tüm sorunları çözmeyecek, belki bir adım iyileşme sağlayacaktır.

Bu tür problemlerde, kullanabileceğimiz diğer akustik parametreleri de ele almak faydalı olacaktır. Birden fazla akustik parametrenin birarada kullanıldığı durumlarda performansta bir adım daha iyileşme gözleme ihtimali yüksektir. Şimdi bu tip (enerjisi ani değişen) sinyallerin başlangıç noktası tespitinde yaygın kullanılan diğer parametrelere geçelim.

  1. Spektral akı (İng: Spectral flux):

Spektral akı, genlik spektrumunun değişimini parametrize etmeye yönelik tasarlanmıştır: ardışık pencerelerden elde edilen genlik spektrumlarının farklarının toplamı şeklinde tanımlanır. \(k\) spektrumdaki örnek endeksi, \(|X_t[k]|\) \(t\). penceredeki genlik spektrumu olmak üzere \(t\) penceresindeki spektral akı \(SF_t\): $\( SF_t = \sum_{k=0}^{N/2} H( |X_t[k]| - |X_{(t-1)}[k])| ) \)\( olarak hesaplanabilir. Burada \)H(x)\( yarım-dalga doğrultma fonksiyonudur: \)\( H(x) = \frac{x+|x|}{2} \)$

  1. Yüksek frekans içeriği (İng: High frequency content): Yüksek frekans içeriği, frekans ile ağırlıklandırılmış genlik spektrumu toplamına karşılık gelir. Bu toplam işleminde, frekans arttıkça karşılık gelen genliğin ağırlığı artmakta, diğer bir deyişle yüksek frekans bileşenlerinin ağırlıklandırmada öne çıkarıldığı bir genlik toplamı hesaplanmaktadır. \(k\) spektrumdaki örnek endeksi, \(|X_t[k]|\) \(t\). penceredeki genlik spektrumu olmak üzere \(t\) penceresindeki yüksek frekans içeriği \(HFC_t:\)

\[ HFC_t = \sum_{k=0}^{N/2} k*|X_t[k]| \]

olarak hesaplanabilir.

Başlangıç anı tespiti için parametrelerde ani değişimin olduğu noktaların bulunması hedeflenir. Bu amaçla oluşturulan serilere de bu sebeple “yenilik/değişiklik fonksiyonlarını” (İng: Novelty functions) adı verilir. Yukarıda tanımladığımız yenilik/değişiklik fonksiyonlarını hesaplamak için Python fonksiyonları tanımlayalım ve örneğimiz üzerinde test edelim.

def spektral_aki(x, pencere_uzunlugu, pencere_kaydirma_miktari, pencere_turu = "hann"):
  '''Girdi olarak verilen x sinyalinin spektral akısını hesaplayıp döndürür'''
  w = ess.Windowing(type = pencere_turu)
  spectrum = ess.Spectrum(size = pencere_uzunlugu)

  # Genlik spektrum farkını hesaplamak için önceki sinyal penceresinin genlik spektrumunu alttaki seride tutacağız 
  onceki_genlik_spektrumu = np.zeros((1 + int(pencere_uzunlugu / 2),))
  sf = [] # her pencere için hesaplanan sf değerini bu listeye ekleyeceğiz
  for kesit in ess.FrameGenerator(x, frameSize = pencere_uzunlugu, hopSize = pencere_kaydirma_miktari):
      genlik_spektrumu = spectrum(w(kesit))
      spektral_fark = genlik_spektrumu - onceki_genlik_spektrumu
      h = (spektral_fark + np.abs(spektral_fark)) / 2
      sf.append(np.sum(h))
      onceki_genlik_spektrumu = genlik_spektrumu # bir sonraki döngü için eldeki spektrumu önceki spektrum olarak kaydediyoruz

  SF = np.array(sf[1:]) # ilk spektral fark sıfırdan çıkartılarak elde edildi, dışarıda bırakalım
  return SF / np.max(SF)

def yuksek_frekans_icerigi(x, pencere_uzunlugu, pencere_kaydirma_miktari, pencere_turu = "hann"):
  '''Girdi olarak verilen x sinyalinin yuksek frekans içeriğini hesaplayıp döndürür'''
  w = ess.Windowing(type = pencere_turu)
  spectrum = ess.Spectrum(size = pencere_uzunlugu)

  hfc = [] # her pencere için hesaplanan hfc değerini bu listeye ekleyeceğiz
  for kesit in ess.FrameGenerator(x, frameSize = pencere_uzunlugu, hopSize = pencere_kaydirma_miktari):
      genlik_spektrumu = spectrum(w(kesit))
      # ağırlıklı toplam işlemi dot-product olarak yapabiliriz, k değerleri de np.arange ile 0 ile N-1 arasında oluşturulabilir
      hfc_t = np.dot(genlik_spektrumu, np.arange(genlik_spektrumu.size))
      hfc.append(hfc_t) 

  hfc = np.array(hfc)
  return hfc / np.max(hfc)

Şimdi aynı sinyal için parametrelerimizi hesaplayıp birarada çizdirelim:

sf = spektral_aki(x, pencere_uzunlugu, pencere_kaydirma_miktari)
hfc = yuksek_frekans_icerigi(x, pencere_uzunlugu, pencere_kaydirma_miktari)

# hfc'ye, içindeki değişimi ön-plana çıkarmak için opsiyonel olarak türev alma ve yarım-dalga doğrultama uygulanabilir 
hfc = np.diff(hfc)
hfc = (hfc + np.abs(hfc)) / 2

# Çizdirme adımları
f, axarr = plt.subplots(4, 1, figsize = (13, 6))
axarr[0].plot(t, x); axarr[0].set_title(dosya); axarr[0].axis('off')
axarr[0].vlines(baslangic_anlari, -1, 1, color='r')
axarr[1].plot(NRG_degisim,label = '(Log) Enerji değişimi)'); axarr[1].axis('off');axarr[1].legend(loc = 1)
axarr[2].plot(sf,label = 'Spektral akı)'); axarr[2].axis('off');axarr[2].legend(loc = 1)
axarr[3].plot(hfc,label = 'Yüksek frekans içeriği değişimi'); axarr[3].axis('off');axarr[3].legend(loc = 1)
<matplotlib.legend.Legend at 0x7fee982b2310>
../_images/10_RitmikAnaliz_25_1.png

Şekil 10.2: Ses kaydı, elle işaretlenmiş başlangıç anları (kırmızı dikey çizgiler) ve sinyalden hesaplanan yenilik/değişim fonksiyonları

Üç akustik parametrenin de benzer değişimler ve tepeler içerdiğini görebiliyoruz. Başlangıç noktası tespiti için üçünü birarada kullanan bir karar mekanizmasının tek parametre kullanan bir mekanizmaya göre daha iyi sonuç verip vermediğini test etmek isteyebilirsiniz. Bunu egzersiz olarak size bırakalım.

Elimizdeki kayıt örneği, enerjisi aniden değişen sinyal kategorisindeydi. Şimdi enerjisi aniden değişmeyen bir kaydı ele alalım. Bu tür bir örneği hazır veri kümesinde bulamadığımız için kendimiz hazırladık (kesit belirleme ve notaların işaretlenmesini elle yaptık ve altta diziler içerisine başlangıç anlarını yazdık)

dosya = 'neyTaksim.mp3'
link = 'https://archive.org/download/cd_ferahnak_bekir-ahin-balolu-and-nurullah-kank/disc1/04.%20Bekir%20%C5%9Eahin%20Balo%C4%9Flu%20And%20Nurullah%20Kan%C4%B1k%20-%20Ney%20taksim_sample.mp3'
urllib.request.urlretrieve(link, dosya);

# Ses sinyalinin dosyadan okunması ve genlik normalizayonu
x = ess.MonoLoader(filename = dosya, sampleRate = ornekleme_fr)()
baslangic_saniye = 14; bitis_saniye = baslangic_saniye + sure_saniye
x = x[baslangic_saniye * ornekleme_fr: bitis_saniye * ornekleme_fr] # sinyalin 5 saniyesini kullanacağız
x = x / np.max(np.abs(x))

# Yazar(bizler) tarafından elle işaretlenmiş başlangıç noktaları
baslangic_anlari = [0.838752, 1.038687, 1.145969, 2.725943, 3.413524, 4.340052]

Audio(x, rate=ornekleme_fr)

Yukarıda tanımladığımız yenilik/değişim fonksiyonlarını hesaplayıp çizdirelim

NRG_degisim = enerji_degisimi(x, pencere_uzunlugu, pencere_kaydirma_miktari)
sf = spektral_aki(x, pencere_uzunlugu, pencere_kaydirma_miktari)
hfc = yuksek_frekans_icerigi(x, pencere_uzunlugu, pencere_kaydirma_miktari)

# hfc'den değişim serisinin elde edilmesi 
hfc = np.diff(hfc)
hfc = (hfc + np.abs(hfc)) / 2

# Çizdirme adımları
f, axarr = plt.subplots(4, 1, figsize = (13, 6))
axarr[0].plot(t, x); axarr[0].set_title(dosya); axarr[0].axis('off')
axarr[0].vlines(baslangic_anlari, -1, 1, color='r')
axarr[1].plot(NRG_degisim,label = '(Log) Enerji değişimi)'); axarr[1].axis('off');axarr[1].legend(loc = 1)
axarr[2].plot(sf,label = 'Spektral akı)'); axarr[2].axis('off');axarr[2].legend(loc = 1)
axarr[3].plot(hfc,label = 'Yüksek frekans içeriği değişimi'); axarr[3].axis('off');axarr[3].legend(loc = 1)
<matplotlib.legend.Legend at 0x7fee9810dca0>
../_images/10_RitmikAnaliz_31_1.png

Şekil 10.3: Ney kaydı, elle işaretlenmiş başlangıç anları (kırmızı dikey çizgiler) ve sinyalden hesaplanan yenilik/değişim fonksiyonları

Bu örnekte, elle işaretlenmiş nota başlangıç anlarını değişim sinyallerinden otomatik tespit etmek zor: çok fazla tepe var ve birçok tepe başlangıç anına denk gelmiyor. Sadece bu üç parametreden yola çıkarak güvenilir başlangıç anı tespiti yapmak kolay/mümkün olmayacaktır.

Enerji/genlik/spektrum değişimi gibi, temel titreşim frekans değişimini de başlangıç anı tespitinde kullanabiliriz. Aşağıda frekans değişimini hesaplayan bir fonksiyon tanımlayalım ve grafiğini çizdirelim. Değişimi log-frekans uzayında gerçekleştireceğiz çünkü 8. defterde ele aldığımız gibi müzikal frekans uzayımızı logaritmik temsil etmeyi tercih ediyoruz.

def frekans_degisimi(x, pencere_uzunlugu, pencere_kaydirma_miktari):
    f0_hz, periyodiklik, periyodiklik_olasiligi = librosa.pyin(x, fmin=50, fmax=2000, sr=ornekleme_fr)
    # Logaritmik dönüşüm: hz -> sent
    A4_hz = 440 # referans frekansını 440 Hz olarak seçelim (başka bir değer de kullanabilirsiniz, değişim hesabı açısından kritik değil)
    f0_sent = np.log2(f0_hz / A4_hz) * 1200;
    f0_fark = np.abs(np.diff(f0_sent))
    return f0_fark
f0_fark = frekans_degisimi(x, pencere_uzunlugu, pencere_kaydirma_miktari)

f, axarr = plt.subplots(2, 1, figsize = (13, 3))
axarr[0].plot(t, x); axarr[0].set_title(dosya); axarr[0].axis('off')
axarr[0].vlines(baslangic_anlari, -1, 1, color='r')
axarr[1].plot(f0_fark, label = '(Log) Temel titreşim frekans değişimi)'); axarr[1].axis('off');axarr[1].legend(loc = 1);
../_images/10_RitmikAnaliz_36_0.png

Şekil 10.4: Ney kaydı, elle işaretlenmiş başlangıç anları (kırmızı dikey çizgiler) ve sinyalden hesaplanan frekans değişim fonksiyonu

Bu ses örneği için, temel titreşim frekansındaki değişimin başlangıç anlarının tespitinde daha faydalı olacağını görüyoruz. Bu küçük deneyden çıkartabileceğimiz sonuç: değişim fonksiyonu tercihimizin ses öğesinin karakterine göre seçilmesi gerekmekte. İki boyutu özellikle dikkate almamız ve şu soruları sormamız gerekiyor:

  • Öğeler/notalar için başlangıç anında enerji aniden değişiyor mu? (öğe “percussive” mi?)

  • Öğe/nota periyodik mi?

Başlangıç anı tespiti ile ilgili temel kavramları ele almış olduk. Konu ilginizi çekti ise Türk Müziği ve Batı müziği enstrümanlarının kayıtlarında başlangıç anı tespit yöntemlerini ele alan bir çalışma için bakınız: Holzapfel et al, 2010.

10.1.2. Tempo ve vuruş anları tespiti#

Vuruş anlarını yukarıda bahsettiğimiz gibi müziğin altında yatan ritmik saatin tik-tak’ları olarak düşünebiliriz ve tempo da dakikadaki vuruşsayısına karşılık gelmektedir (İng: beats per minute (BPM)).

Notalar farklı uzunluklarda olduğu ve arada sus’lar da bulunduğu için her vuruş anına bir nota başlangıç anı denk gelmemekle birlikte birçok müzik türünde çoğu nota başlangıç anı bir vuruşa denk gelmektedir. Diğer bir deyişle nota başlangıç anları ritim ızgarasının üzerine oturmaktadır. Peki elimizdeki bir kaydın ritim ızgarasını nasıl bulabiliriz? Öncelikle içinde sadece ritmik enstrüman icrası bulunduran bir kaydın spektral akısını dalga formu ile beraber çizdirelim. Bulduğumuz tempo değerini doğrulayabilmek için müzisyenin şarkının temposunu açıkça belirttiği bir kayıt kullanalım: https://www.youtube.com/watch?v=HTmKgbT3PFA&

# Youtube'dan ses dosyası indirmek için kullanacağımız kütüphaneyi kuralım
!pip install youtube_dl
Requirement already satisfied: youtube_dl in /home/baris/miniconda3/envs/KitapYazim/lib/python3.9/site-packages (2021.12.17)
from __future__ import unicode_literals
import youtube_dl

ydl_opts = {
    'format': 'bestaudio/best',
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }],
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
    ydl.download(['https://www.youtube.com/watch?v=HTmKgbT3PFA&'])
[youtube] HTmKgbT3PFA: Downloading webpage
[download] Resuming download at byte 3933262
[download] Destination: 120 BPM - Simple Straight Beat - Drum Track _ Loop-HTmKgbT3PFA.m4a
[download]  77.9% of 4.82MiB at  6.98KiB/s ETA 02:36
[download]  77.9% of 4.82MiB at 20.82KiB/s ETA 00:52
[download]  78.0% of 4.82MiB at 43.71KiB/s ETA 00:24
[download]  78.2% of 4.82MiB at 93.02KiB/s ETA 00:11
[download]  78.5% of 4.82MiB at 83.72KiB/s ETA 00:12
[download]  79.2% of 4.82MiB at 72.67KiB/s ETA 00:14
[download]  80.5% of 4.82MiB at 69.02KiB/s ETA 00:13
[download]  81.8% of 4.82MiB at 63.40KiB/s ETA 00:14
[download]  82.9% of 4.82MiB at 65.75KiB/s ETA 00:12
[download]  84.4% of 4.82MiB at 65.07KiB/s ETA 00:11
[download]  85.7% of 4.82MiB at 65.03KiB/s ETA 00:10
[download]  87.0% of 4.82MiB at 65.09KiB/s ETA 00:09
[download]  88.4% of 4.82MiB at 65.36KiB/s ETA 00:08
[download]  89.7% of 4.82MiB at 65.78KiB/s ETA 00:07
[download]  91.1% of 4.82MiB at 66.28KiB/s ETA 00:06
[download]  92.6% of 4.82MiB at 65.51KiB/s ETA 00:05
[download]  93.8% of 4.82MiB at 66.51KiB/s ETA 00:04
[download]  95.4% of 4.82MiB at 65.30KiB/s ETA 00:03
[download]  96.5% of 4.82MiB at 65.91KiB/s ETA 00:02
[download]  98.1% of 4.82MiB at 65.78KiB/s ETA 00:01
[download]  99.4% of 4.82MiB at 65.79KiB/s ETA 00:00
[download] 100.0% of 4.82MiB at 66.32KiB/s ETA 00:00
[download] 100% of 4.82MiB in 00:16
[ffmpeg] Correcting container in "120 BPM - Simple Straight Beat - Drum Track _ Loop-HTmKgbT3PFA.m4a"
[ffmpeg] Destination: 120 BPM - Simple Straight Beat - Drum Track _ Loop-HTmKgbT3PFA.mp3
Deleting original file 120 BPM - Simple Straight Beat - Drum Track _ Loop-HTmKgbT3PFA.m4a (pass -k to keep)
dosya = '120 BPM - Simple Straight Beat - Drum Track _ Loop-HTmKgbT3PFA.mp3'
# Ses sinyalinin dosyadan okunması ve genlik normalizayonu
x = ess.MonoLoader(filename = dosya, sampleRate = ornekleme_fr)()
baslangic_saniye = 3; bitis_saniye = 13
x = x[baslangic_saniye * ornekleme_fr: bitis_saniye * ornekleme_fr] # sinyalin 3-13 sn arasını kullanacağız
x = x / np.max(np.abs(x))

sf = spektral_aki(x, pencere_uzunlugu, pencere_kaydirma_miktari)

f, axarr = plt.subplots(2, 1, figsize = (13, 4))
axarr[0].plot(x); axarr[0].set_title(dosya); axarr[0].axis('off')
axarr[1].plot(sf,label = 'Spektral akı)'); axarr[1].axis('off');axarr[1].legend(loc = 1);
../_images/10_RitmikAnaliz_43_0.png

Şekil 10.5: 120 BPM’de bir davul kaydı dalga formu (üstteki şekil) ve sinyalden hesaplanan spektral akı fonksiyonu (alttaki şekil)

Kaydı dinleyelim:

Audio(x, rate=ornekleme_fr)

Spektral akı sinyalinin tepelerinin yerleşimi sanki bir ızgaranın üzerine oturmuş gibi. Tepeler arasındaki uzaklıklar birbirine yakın (veya birbirinin tam katı uzaklıklar var). Buradan, alttan yatan bir periyod olduğu düşünülebilir. Peki bu periyodu nasıl bulabiliriz?

8 numaralı defterimizde otokorelasyon fonksiyonu yardımıyla periyot kestirimi yapabileceğimizi görmüştük. Burada tekrar kullanmayı deneyelim. Otokorelasyon, sinyalin kaydırılmış haliyle skaler çarpıma tabi tutulmasıyla hesaplanıyordu. Spektral akı sinyali için çeşitli \(k\) kaydırma değerlerinde hesaplayıp çizdirelim.

\[ SF_r[k] = \sum_n SF[n] SF[n+k] \]
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

# Fonksiyonun elimizdeki sf serisi kullanılarak çağırılması
sf_r = otokorelasyon(sf)

Hesapladığımız otokorelasyon sinyalinin tümünü ve yakın gösterim için baş kısmını çizdirelim.

fig = plt.figure(figsize=(8,5))
plt.subplot(2,1,1)
plt.plot(sf_r);
plt.subplot(2,1,2)
plt.plot(sf_r[:250]);
plt.ylabel('Spektral akı otokorelasyonu');plt.xlabel('Zaman (pencere endeksi)')
Text(0.5, 0, 'Zaman (pencere endeksi)')
../_images/10_RitmikAnaliz_50_1.png

Şekil 10.5: Bir davul kaydından hesaplanan spektral akı sinyalinden hesaplanan otokorelasyon sinyali (alttaki figür üsttekinin yakınlaştırılmış halidir)

Spektral akı otokorelasyonundaki ilk güçlü tepenin lokasyonu bize spektral akının periyodunu verecektir. Gözlemle tepenin 30-50 endeksleri arasında olduğunu görebiliyoruz. Bu bilgiyi kullanıp tepe yerini buldurarak tempo bilgisini elde etmeye çalışalım.

periyod_SF = np.argmax(sf_r[30:50]) + 30 # belirli aralıktaki tepenin endeksinin bulunması
print('Periyod:', periyod_SF, '(bu değer pencere endeksi türündendir)')
Periyod: 43 (bu değer pencere endeksi türündendir)

8 no’lu defterde periyod - frekans dönüşümü yapmıştık. Frekansı örnekleme frekansını periyoda bölerek bulmuştuk. Burada da aynı yöntemi kullanabiliriz, ancak örnekleme frekansı için spektral akının örnekleme frekansını kullanmalıyız (çünkü otokorelasyon sinyalini spektral akı üzerinden hesapladık). Spektral akı sinyalinin örnekleme frekansını nasıl bulabiliriz?

Spektral akı sinyalinde her ses sinyali penceresi bir örnekle temsil edilmektedir ve ardışık pencereler arasında “pencere kaydırma miktarı” kadar ses sinyali örneği vardır. 1 saniyede 44100 ses örneği varsa, 1 saniyede kaç pencere vardır sorusunun cevabı bize spektral akı sinyalinin örnekleme frekansını verir. Aşağıdaki kod parçası ile hesaplayabiliriz.

ornekleme_fr_SF = ornekleme_fr / pencere_kaydirma_miktari
print('Spektral akı sinyali örnekleme frekansı:', ornekleme_fr_SF, 'Hz')
Spektral akı sinyali örnekleme frekansı: 86.1328125 Hz

Saniyede 86.13 adet ses sinyali penceresi varmış. Şimdi periyod-frekans dönüşüm işlemini yapabiliriz

frekans = ornekleme_fr_SF / periyod_SF # saniyedeki vuruşsayısı
tempo = frekans * 60 # saniyedeki vuruşsayısını dakikadaki vuruşsayısına dönüştürmek için 60 ile çarpalım
print('Tempo:', tempo, 'bpm')
Tempo: 120.18531976744187 bpm

Kayıtta gerçek değer 120 bpm olarak verilmiş ve buna çok yakın bir değer bulduk. Sadece davul kaydı içermesi nedeniyle bu görece kolay bir örnekti.

Şimdi 100 bpm’de başka enstrümanlar da içeren bir kaydı kullanalım: https://www.youtube.com/shorts/3w5Yuc-2IMg

ydl_opts = {
    'format': 'bestaudio/best',
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }],
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
    ydl.download(['https://www.youtube.com/watch?v=cKbRHkVJlUg'])
[youtube] cKbRHkVJlUg: Downloading webpage
[youtube] cKbRHkVJlUg: Downloading MPD manifest
[download] Resuming download at byte 1600929
[download] Destination: Play-along Backing Track - G Major New Orleans Blues 100bpm-cKbRHkVJlUg.webm
[download]  44.7% of 3.42MiB at  6.91KiB/s ETA 04:40
[download]  44.7% of 3.42MiB at 20.17KiB/s ETA 01:36
[download]  44.8% of 3.42MiB at 42.83KiB/s ETA 00:45
[download]  45.1% of 3.42MiB at 88.86KiB/s ETA 00:21
[download]  45.5% of 3.42MiB at 84.62KiB/s ETA 00:22
[download]  46.4% of 3.42MiB at 73.59KiB/s ETA 00:25
[download]  48.2% of 3.42MiB at 69.78KiB/s ETA 00:25
[download]  50.1% of 3.42MiB at 63.90KiB/s ETA 00:27
[download]  51.7% of 3.42MiB at 66.41KiB/s ETA 00:25
[download]  53.9% of 3.42MiB at 65.04KiB/s ETA 00:24
[download]  55.7% of 3.42MiB at 64.12KiB/s ETA 00:24
[download]  57.4% of 3.42MiB at 67.18KiB/s ETA 00:22
[download]  60.1% of 3.42MiB at 66.51KiB/s ETA 00:21
[download]  61.9% of 3.42MiB at 66.40KiB/s ETA 00:20
[download]  63.8% of 3.42MiB at 66.56KiB/s ETA 00:19
[download]  65.8% of 3.42MiB at 66.91KiB/s ETA 00:17
[download]  67.8% of 3.42MiB at 66.11KiB/s ETA 00:17
[download]  69.4% of 3.42MiB at 65.75KiB/s ETA 00:16
[download]  71.2% of 3.42MiB at 66.77KiB/s ETA 00:15
[download]  73.6% of 3.42MiB at 66.06KiB/s ETA 00:13
[download]  75.3% of 3.42MiB at 66.71KiB/s ETA 00:12
[download]  77.6% of 3.42MiB at 66.78KiB/s ETA 00:11
[download]  79.6% of 3.42MiB at 66.06KiB/s ETA 00:10
[download]  81.1% of 3.42MiB at 66.39KiB/s ETA 00:09
[download]  83.3% of 3.42MiB at 66.15KiB/s ETA 00:08
[download]  85.0% of 3.42MiB at 66.08KiB/s ETA 00:07
[download]  86.9% of 3.42MiB at 66.13KiB/s ETA 00:06
[download]  88.8% of 3.42MiB at 66.29KiB/s ETA 00:05
[download]  90.8% of 3.42MiB at 66.53KiB/s ETA 00:04
[download]  92.9% of 3.42MiB at 66.23KiB/s ETA 00:03
[download]  94.6% of 3.42MiB at 66.05KiB/s ETA 00:02
[download]  96.3% of 3.42MiB at 66.57KiB/s ETA 00:01
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
/tmp/ipykernel_9718/3665930586.py in <module>
      8 }
      9 with youtube_dl.YoutubeDL(ydl_opts) as ydl:
---> 10     ydl.download(['https://www.youtube.com/watch?v=cKbRHkVJlUg'])

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/YoutubeDL.py in download(self, url_list)
   2066             try:
   2067                 # It also downloads the videos
-> 2068                 res = self.extract_info(
   2069                     url, force_generic_extractor=self.params.get('force_generic_extractor', False))
   2070             except UnavailableVideoError:

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/YoutubeDL.py in extract_info(self, url, download, ie_key, extra_info, process, force_generic_extractor)
    806                                     'and will probably not work.')
    807 
--> 808             return self.__extract_info(url, ie, download, extra_info, process)
    809         else:
    810             self.report_error('no suitable InfoExtractor for URL %s' % url)

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/YoutubeDL.py in wrapper(self, *args, **kwargs)
    813         def wrapper(self, *args, **kwargs):
    814             try:
--> 815                 return func(self, *args, **kwargs)
    816             except GeoRestrictedError as e:
    817                 msg = e.msg

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/YoutubeDL.py in __extract_info(self, url, ie, download, extra_info, process)
    845         self.add_default_extra_info(ie_result, ie, url)
    846         if process:
--> 847             return self.process_ie_result(ie_result, download, extra_info)
    848         else:
    849             return ie_result

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/YoutubeDL.py in process_ie_result(self, ie_result, download, extra_info)
    879         if result_type == 'video':
    880             self.add_extra_info(ie_result, extra_info)
--> 881             return self.process_video_result(ie_result, download=download)
    882         elif result_type == 'url':
    883             # We have to add extra_info to the results because it may be

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/YoutubeDL.py in process_video_result(self, info_dict, download)
   1690                 new_info = dict(info_dict)
   1691                 new_info.update(format)
-> 1692                 self.process_info(new_info)
   1693         # We update the info dict with the best quality format (backwards compatibility)
   1694         info_dict.update(formats_to_download[-1])

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/YoutubeDL.py in process_info(self, info_dict)
   1974                 else:
   1975                     # Just a single file
-> 1976                     success = dl(filename, info_dict)
   1977             except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
   1978                 self.report_error('unable to download video data: %s' % error_to_compat_str(err))

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/YoutubeDL.py in dl(name, info)
   1913                     if self.params.get('verbose'):
   1914                         self.to_screen('[debug] Invoking downloader on %r' % info.get('url'))
-> 1915                     return fd.download(name, info)
   1916 
   1917                 if info_dict.get('requested_formats') is not None:

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/downloader/common.py in download(self, filename, info_dict)
    364             time.sleep(sleep_interval)
    365 
--> 366         return self.real_download(filename, info_dict)
    367 
    368     def real_download(self, filename, info_dict):

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/downloader/http.py in real_download(self, filename, info_dict)
    350             try:
    351                 establish_connection()
--> 352                 return download()
    353             except RetryDownload as e:
    354                 count += 1

~/miniconda3/envs/KitapYazim/lib/python3.9/site-packages/youtube_dl/downloader/http.py in download()
    236                 try:
    237                     # Download and write
--> 238                     data_block = ctx.data.read(block_size if data_len is None else min(block_size, data_len - byte_counter))
    239                 # socket.timeout is a subclass of socket.error but may not have
    240                 # errno set

~/miniconda3/envs/KitapYazim/lib/python3.9/http/client.py in read(self, amt)
    461             # Amount is given, implement using readinto
    462             b = bytearray(amt)
--> 463             n = self.readinto(b)
    464             return memoryview(b)[:n].tobytes()
    465         else:

~/miniconda3/envs/KitapYazim/lib/python3.9/http/client.py in readinto(self, b)
    505         # connection, and the user is reading more bytes than will be provided
    506         # (for example, reading in 1k chunks)
--> 507         n = self.fp.readinto(b)
    508         if not n and b:
    509             # Ideally, we would raise IncompleteRead if the content-length

~/miniconda3/envs/KitapYazim/lib/python3.9/socket.py in readinto(self, b)
    702         while True:
    703             try:
--> 704                 return self._sock.recv_into(b)
    705             except timeout:
    706                 self._timeout_occurred = True

~/miniconda3/envs/KitapYazim/lib/python3.9/ssl.py in recv_into(self, buffer, nbytes, flags)
   1239                   "non-zero flags not allowed in calls to recv_into() on %s" %
   1240                   self.__class__)
-> 1241             return self.read(nbytes, buffer)
   1242         else:
   1243             return super().recv_into(buffer, nbytes, flags)

~/miniconda3/envs/KitapYazim/lib/python3.9/ssl.py in read(self, len, buffer)
   1097         try:
   1098             if buffer is not None:
-> 1099                 return self._sslobj.read(len, buffer)
   1100             else:
   1101                 return self._sslobj.read(len)

KeyboardInterrupt: 
dosya = 'Play-along Backing Track - G Major New Orleans Blues 100bpm-cKbRHkVJlUg.mp3'
x = ess.MonoLoader(filename = dosya, sampleRate = ornekleme_fr)()
baslangic_saniye = 5; bitis_saniye = 35
x = x[baslangic_saniye * ornekleme_fr: bitis_saniye * ornekleme_fr] # sinyalin 3.5-13.5 sn arasını kullanacağız
x = x / np.max(np.abs(x))
Audio(x, rate=ornekleme_fr)
Output hidden; open in https://colab.research.google.com to view.

Kaydı dinleyiniz.

Tempo bulmak için yukarıda kullandığımız adımları listeleyelim:

  • Spektral akı sinyalinin hesaplanması

  • Spektral akının otokorelasyonunun hesaplanması

  • Otokorelasyon sinyalinde tepenin endeksinin periyod olarak bulunması

  • Periyod bilgisinin dakikadaki vuruş sayısına dönüştürülmesi

8 no’lu defterimizde otokorelasyon temelli periyod bulma yönteminde tüm tepeleri aramaktansa belirli bir aralıkta tepeleri aramanın iki kat veya yarısı değerler bulunması problemini azalttığından bahsetmiştik. Bu örnekte de minimum-tempo ve maksimum-tempo değerleri tanımlayarak fonksiyonun işini kolaylaştıralım. Yukarıdaki süreci gerçekleyen bir fonksiyon tanımlayıp kaydımız üzerinde test edelim.

def tempo_bul(x, pencere_uzunlugu, pencere_kaydirma_miktari, min_tempo=70, max_tempo=250):
  sf = spektral_aki(x, pencere_uzunlugu, pencere_kaydirma_miktari)
  sf_r = otokorelasyon(sf)
  # Tempo/frekans bilgisini periyod bilgisine çevirelim
  max_periyod = int(ornekleme_fr_SF / (min_tempo / 60))
  min_periyod = int(ornekleme_fr_SF / (max_tempo / 60))
  # min_periyod - max_periyod arasındaki tepenin endeksinin bulunması
  periyod_SF = np.argmax(sf_r[min_periyod:max_periyod]) + min_periyod
  tempo = int(np.round(60 * ornekleme_fr_SF / periyod_SF)) # tempo genelde tamsayı olarak ifade edilir, yuvarlayalım
  return tempo

tempo = tempo_bul(x, pencere_uzunlugu, pencere_kaydirma_miktari)
print('Tempo:', tempo, 'bpm')
Tempo: 99 bpm

Tempoyu hemen hemen doğru bir şekilde tespit edebildik.

Peki, kayıt boyunca tempo değişiyor ise ne yapabiliriz? Diğer birçok probleme uyguladığımız gibi, kaydı kesitlerine ayırabilir ve kesitler için ölçüm yapar ve bir tempo serisi elde edip çizdirebiliriz.

Bir alternatif de, sayısal periyod değerlerini bulmak yerine her kesit için hesaplanan spektral akı (veya başka yenilik/değişim fonksiyonu) otokorelasyon vektörünü kolonlara dizerek bir matris elde edip onu çizdirmektir. Bu matrise tempogram ismi verilir. Tempogramın oluşturulması daha önce çok defa ele aldığımız ses spektrogramının oluşturulma sürecine çok benzer. Spektrogram, yanyana dizilen genlik spektrumlarından oluşurken, tempogram, yanyana dizilen otokorelasyon fonksiyonlarından oluşur (otokorelasyon yerine yenilik/değişim fonksiyonunun genlik spektrumunun kullanıldığı bir sürümü de mevcuttur).

Öncelikle temposu zamanla değişen bir kaydı indirelim ve dinleyelim.

url = 'https://github.com/MTG/MIRCourse/raw/master/data/baris/varyingTempoBlues.mp3'
dosya_ismi = url.split('/')[-1]
urllib.request.urlretrieve(url, dosya_ismi)

x = ess.MonoLoader(filename = dosya_ismi, sampleRate = ornekleme_fr)()
Audio(x, rate=ornekleme_fr)

Şimdi kendimiz tempogram oluşturup çizdirebiliriz. Tempogram oluşturulması için librosa kütüphanesinde de bir fonksiyon (librosa.feature.tempogram) bulunmaktadır. Onu da inceleyebilir ve deneyebilirsiniz.

# Tempogram hesabı
SF = spektral_aki(x,pencere_uzunlugu,pencere_kaydirma_miktari)
SFpencere_uzunlugu = 1000
SFpencere_kaydirma_miktari = 250
baslangic_endeksleri = np.arange(0, SF.size - SFpencere_uzunlugu, SFpencere_kaydirma_miktari, dtype = int)
pencere_sayisi = baslangic_endeksleri.size
tempogram = np.array([]).reshape(0, SFpencere_uzunlugu)
w = get_window(('tukey', 0.5), SFpencere_uzunlugu)
for k in range(pencere_sayisi):
    baslangic_endeksi = baslangic_endeksleri[k]
    SF_k = SF[baslangic_endeksi : baslangic_endeksi + SFpencere_uzunlugu] * w
    # Otokorelasyon 
    sf_r = np.zeros_like(SF_k, dtype = float)
    for k in range(1, SFpencere_uzunlugu - 1):
        SF_k_shifted = np.hstack((np.zeros(k),SF_k[:-k]))
        sf_r[k] = np.dot(SF_k, SF_k_shifted)
    tempogram = np.vstack((tempogram, sf_r))

# Tempogramın çizdirilmesi
zaman_x = np.arange(tempogram.shape[0]) * (pencere_kaydirma_miktari * SFpencere_kaydirma_miktari) / float(ornekleme_fr)
zaman_y = np.arange(tempogram.shape[1]) * pencere_kaydirma_miktari / float(ornekleme_fr)

plt.figure(figsize=(12, 4))
plt.pcolormesh(zaman_x, zaman_y, np.transpose(tempogram))
plt.xlim([0, zaman_x[-1]])
plt.ylim([0, 3]) # alternatif: plt.ylim([0, zaman_y[-1]])
plt.title('Tempogram (Spektrak akı otokorelayonu)')
plt.ylabel('Kaydırma miktarı (zaman, saniye)')
plt.xlabel('Zaman (pencere endeksi)');
../_images/10_RitmikAnaliz_72_0.png

Şekil 10.6: Temposu giderek artan bir kaydın spektral akı otokorelasyonundan elde edilen tempogram gösterimi

Şekil 10.6’da gördüğümüz tempogram bir yanıyla daha önce ele aldığımız spektrogram gösterimine benzemektedir: her pencere için hesaplanan bir fonksiyonun kolonlara yerleştirilmesiyle zamanla değişimi gösteren bir temsil elde edilmiştir. Önemli fark y ekseninin frekans değil kaydırma miktarı / zaman cinsinden olmasıdır. Spektrogramda gördüğümüz harmoniklere benzer tam katlarda izler görüyoruz. İzlerin y eksenindeki değeri zamanla azalıyor; periyod zamanla küçülüyor, tempo giderek artıyor.

Tempo kestirme ve değişimini görselleştirme işlemleri için örnekler gördük. Son olarak vuruş anlarının otomatik tespiti için Essentia kütüphanesi içerisindeki bir fonksiyonun kullanımına dair bir örnek sunalım. Tempo(dakikadaki vuruşsayısı) bulunabildiğinde, vuruş anlarının tespiti, başlangıç noktalarıyla senkron olacak şekilde vuruların yerleştirilmesine karşılık gelen bir işlem olarak düşünülebilir. Aşağıdaki örnekleri inceleyiniz.

# Defterin başında indirdiğimiz ses dosyalarını kullanalım
ses_dosyalari = ['25-rujero.wav', 'RM-C036.wav', '3-you_think_too_muchb.wav', 'tiersen11.wav']
ornekleme_fr = 44100
sure_saniye = 5
t = np.arange(sure_saniye * ornekleme_fr) / float(ornekleme_fr)
sifirlar_serisi = t * 0 # çizimler sırasında kullanılan içi sıfır dolu bir seri

# Kullanacağımız Essentia fonksiyonlarını tanımlayalım
od_hfc = ess.OnsetDetection(method = 'hfc')
w = ess.Windowing(type = 'hann')
fft = ess.FFT() 
c2p = ess.CartesianToPolar() # kompleks değerli spektrumdan genlik ve faz spektrumu elde etmek için kullanacağız
onsets = ess.Onsets()
for i, dosya in enumerate(ses_dosyalari):
    x = ess.MonoLoader(filename = dosya, sampleRate = ornekleme_fr)()
    x = x[:sure_saniye * ornekleme_fr]#let's use only the first two seconds of the signals
    x = x / np.max(np.abs(x))
    
    # Elle işaretlenmiş başlangıç anlarının dosyadan okunması işlemi
    isaretli_baslangic_anlari = np.loadtxt(dosya.replace('.wav','.txt'))
    isaretli_baslangic_anlari = isaretli_baslangic_anlari[isaretli_baslangic_anlari < sure_saniye]
    
    # Çizdirme işlemleri
    f, axarr = plt.subplots(3, 1, figsize=(13, 4))
    axarr[0].plot(t,x); axarr[0].set_title('Kayıt:' + dosya); axarr[0].axis('off')
    axarr[0].vlines(isaretli_baslangic_anlari, -1, 1, color = 'r')
    
    # Essentia ile başlangıç anları tespiti ('stream mode' kullanılarak: https://essentia.upf.edu/streaming_architecture.html)
    pool = Pool()
    for frame in ess.FrameGenerator(x, frameSize = 1024, hopSize = 512):
        mag, phase, = c2p(fft(w(frame)))
        pool.add('features.hfc', od_hfc(mag, phase))
    
    hfc_baslangic_anlari = onsets(array([pool['features.hfc']]),[1])
        
    axarr[1].plot(t,sifirlar_serisi);
    axarr[1].axis('off')
    axarr[1].vlines(hfc_baslangic_anlari, -1, 1, color='b', label='HFC ile tespit edilen başlangıç anları');
    axarr[1].legend();
    
    # Essentia ile vuruş anları tespiti 
    beatTracker = ess.BeatTrackerDegara() 
    ticks = beatTracker(x)
    
    axarr[2].plot(t,sifirlar_serisi);
    axarr[2].axis('off')
    axarr[2].vlines(ticks, -1, 1, color = 'r', linewidth=4, label='Tespit edilen vuruş anları');
    axarr[2].legend();
../_images/10_RitmikAnaliz_77_0.png ../_images/10_RitmikAnaliz_77_1.png ../_images/10_RitmikAnaliz_77_2.png ../_images/10_RitmikAnaliz_77_3.png

Şekil 10.7: Dört farklı kayıt için i) dalga formu ve elle işaretlenmiş başlangıç anları, ii) HFC kullanılarak otomatik bulunmuş başlangıç anları, iii) Essentia BeatTrackerDegara() fonksiyonu kullanılarak otomatik tespit edilmiş vuruş anları

Şekil 10.7’yi incelediğimizde otomatik tespit edilen vuruş anlarının defterin başında değindiğimiz ritim ızgarasının tik-taklarına karşılık geldiğini, zamana neredeyse eşit aralıklarla yerleştirildiklerini ve bazı başlangıç anları ile senkron olduklarını görebiliyoruz.

Bu defterde ele alınan konular ilginizi çekti ise mirdata kütüphanesini kullanarak ritmik öğeleri elle işaretlenmiş veriler içeren veritabanlarından indirip üzerinde çalışmaya başlayabilirsiniz.

Benzer içerikte diğer kaynaklar:

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