Ritmik Analiz
Contents
10. Ritmik Analiz#
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);
Ş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.
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} \)$
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:\)
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>
Ş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>
Ş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);
Ş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);
Ş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.
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)')
Ş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)');
Ş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();
Ş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:
https://github.com/MTG/MIRCourse/blob/master/notebooks/Lecture4_2_visualizeAnnotations.ipynb
Çok daha kapsamlı bir kaynak: https://www.audiolabs-erlangen.de/resources/MIR/FMP/C6/C6.html
Yazar: Barış Bozkurt, editör: Ahmet Uysal