9. Entonasyon Analizi#

Open In Colab

9.1. Entonasyon Analizi#

Bir önceki defterimizde monofonik bir ses kaydında icra edilen ezgiyi sinyalin ardışık küçük kesitlerinde temel titreşim frekansı kestirimi yapıp bu değerlerden bir seri oluşturarak temsil etmiştik. Çizdirdiğimizde frekansın zamanla nasıl değiştiğini gözleyebildiğimiz bu dizilere frekans (zaman) serisi/dizisi adı verebiliriz. Bu defterde, ses sinyalinden otomatik analizle elde ettiğimiz bu dizilerin işlenmesini ele alacağız.

Frekans dizilerinin işlendiği birçok müzik sinyal işleme problemi vardır. Örneğin frekans dizisinden yola çıkarak nota dizisi tespit etmek, otomatik notaya dökme işlemi gerçekleştirmek isteyebiliriz. Veya elimizde eserin notası var ise nota ile ses kaydı arasında bir hizalama (zamanda eşleme) işlemi yapmak isteyebiliriz.

Bu defterde örnek uygulama olarak şu problemi ele alacağız: Klasik ve pop türünde Batı müziği kayıtları için nota frekanslarına dair yaygın kabul gören bir standart bulunmaktadır ve standart gerçek icra ile büyük oranda uyuşmaktadır. Ancak bu durum birçok müzik kültürü için geçerli değildir; bazı müzik kültürlerinde yaygın kabul gören standart bulunmamakta, bazılarında ise kabul gören standart bulunmakla beraber icra ile bazı yönlerden uyuşmazlık gözlenmektedir. İcra ile kuram arasında uyuşmazlıkların gözlendiği durumlarda ses sinyal işleme bize problemi tespit etme ve anlama konusunda yardımcı olabilir.

Örneğin Türk makam müziğinde Hüzzam makamını ele alalım. Bu makamda icra edilen müzikal aralıkların (notaların frekansları arası oranlar/uzaklıklar) yaygın kullanılan kuramda açıklanan ile uyumlu olmadığına birçok kaynakta değinilmektedir (örnek). Bu makamda bir icranın kaydından yola çıkarak otomatik analiz ile kayıtta kullanılan müzikal aralıkları tespit edebilir miyiz? Bu defterimizde bu problemi ele alalım.

Bunun için ilk olarak bir dizi kısa kaydı indirecek, frekans kestirimi yapıp frekans serilerini elde edeceğiz. Aşağıdaki hücreleri çalıştırarak bu işlemi gerçekleştirin ve ses kayıtlarını dinleyin.

İlk olarak kütüphanelerimiz kurup yükleyelim:

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

Hüzzam makamında 3 adet kayıt indirelim.

baglantilar = {'Tanburi Cemil':'https://archive.org/download/cd_volumes-2-3_tanburi-cemil-bey/disc2/02.01.%20Tanburi%20Cemil%20Bey%20-%20Huzzam%20Taksim_sample.mp3',
               'Şükrü Tunar':'https://archive.org/download/cd_istanbul-1925_various-artists-deniz-kizikanuni-artaki-ha/disc1/01.%20Sukru%20Tunar%20-%20Huzzam%20Taksim_sample.mp3',
               'Udi Hrant':'https://archive.org/download/06UdiHrantKenkulianHzzamArkrBaharhGrmedenYazGeldiGedtiHoknadzDurtmadz/06%20-%20Udi%20Hrant%20Kenkulian%20-%20H%C3%BCzzam%20%C5%9Eark%C4%B1%20Bahar%C4%B1%20G%C3%B6rmeden%20Yaz%20Geldi%20Ge%C3%A7ti%20-%20Hoknadz%20Durtmadz.mp3'
              }
               
dosyalar = []
for muzisyen, link in baglantilar.items():
  dosya = muzisyen + '.mp3'
  dosyalar.append(dosya)
  urllib.request.urlretrieve(link, dosya)
  print(dosya, 'indirildi')
Tanburi Cemil.mp3 indirildi
Şükrü Tunar.mp3 indirildi
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
/tmp/ipykernel_9621/2934216479.py in <module>
      8   dosya = muzisyen + '.mp3'
      9   dosyalar.append(dosya)
---> 10   urllib.request.urlretrieve(link, dosya)
     11   print(dosya, 'indirildi')

~/miniconda3/envs/KitapYazim/lib/python3.9/urllib/request.py in urlretrieve(url, filename, reporthook, data)
    266 
    267             while True:
--> 268                 block = fp.read(bs)
    269                 if not block:
    270                     break

~/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: 

Kayıtların ilk 30 saniyelik kısımlarını alarak Librosa.pyin fonksiyonu ile frekans serilerini elde edelim. Serileri çizdirelim.

ornekleme_fr = 44100
max_uzunluk = 30 # saniye cinsinden maksimum uzunluk
minF0 = 50 # Hz cinsinden gözlenmesi beklenen minimum frekans
maxF0 = 800 # Hz cinsinden gözlenmesi beklenen maksimum frekans

frekans_serileri = {} # dosya ismi ile frekans serisini eşleyen bir kütüphane oluşturalım
print('PYIN frekans kestirimi gerçekleştiriliyor')
for dosya in dosyalar:
  muzik_sinyali = ess.MonoLoader(filename=dosya, sampleRate=ornekleme_fr)()

  # Sinyal uzun ise kırpalım
  muzik_sinyali = muzik_sinyali[:min(muzik_sinyali.size, int(max_uzunluk*ornekleme_fr))]

  # PYIN ile temel titreşim frekans kestirimi
  frekans_serisi_pyin, periyodiklik, periyodiklik_olasiligi = librosa.pyin(muzik_sinyali, fmin=minF0, fmax=maxF0, sr=ornekleme_fr)
  frekans_serileri[dosya] = frekans_serisi_pyin
  print(dosya, 'için frekans kestirimi tamamlandı')
PYIN frekans kestirimi gerçekleştiriliyor
Tanburi Cemil.mp3 için frekans kestirimi tamamlandı
Şükrü Tunar.mp3 için frekans kestirimi tamamlandı
Udi Hrant.mp3 için frekans kestirimi tamamlandı

Şimdi elde ettiğimiz frekans serilerini çizdirelim ve ses kayıtlarını dinleyelim.

dosya = dosyalar[0] # ilk kayıt

fig = plt.figure(figsize=(15,3))
plt.plot(frekans_serileri[dosya])
plt.title(dosya + " frekans serisi");
plt.xlabel('zaman (analiz pencere endeksi)');
plt.ylabel('f0(Hz)');
plt.grid()
../_images/09_EntonasyonAnalizi_13_0.png

Şekil 9.1: Tanburi Cemil kaydının temel titreşim frekans serisi

muzik_sinyali = ess.MonoLoader(filename=dosya, sampleRate=ornekleme_fr)()
muzik_sinyali = muzik_sinyali[:min(muzik_sinyali.size, int(max_uzunluk*ornekleme_fr))]
Audio(muzik_sinyali, rate=ornekleme_fr)
dosya = dosyalar[1] # ikinci kayıt

fig = plt.figure(figsize=(15,3))
plt.plot(frekans_serileri[dosya])
plt.title(dosya + " frekans serisi");
plt.xlabel('zaman (analiz pencere endeksi)');
plt.ylabel('f0(Hz)');
plt.grid()
../_images/09_EntonasyonAnalizi_16_0.png

Şekil 9.2: Şükrü Tunar kaydının temel titreşim frekans serisi

muzik_sinyali = ess.MonoLoader(filename=dosya, sampleRate=ornekleme_fr)()
muzik_sinyali = muzik_sinyali[:min(muzik_sinyali.size, int(max_uzunluk*ornekleme_fr))]
Audio(muzik_sinyali, rate=ornekleme_fr)
dosya = dosyalar[2] # üçüncü kayıt

fig = plt.figure(figsize=(15,3))
plt.plot(frekans_serileri[dosya])
plt.title(dosya + " frekans serisi");
plt.xlabel('zaman (analiz pencere endeksi)');
plt.ylabel('f0(Hz)');
plt.grid()
../_images/09_EntonasyonAnalizi_19_0.png

Şekil 9.3: Udi Hrant kaydının temel titreşim frekans serisi

muzik_sinyali = ess.MonoLoader(filename=dosya, sampleRate=ornekleme_fr)()
muzik_sinyali = muzik_sinyali[:min(muzik_sinyali.size, int(max_uzunluk*ornekleme_fr))]
Audio(muzik_sinyali, rate=ornekleme_fr)

Ezginin zamanla değişimini gösteren grafikler elde etmiş olduk. Peki kullanılan müzikal aralıkları nasıl ölçebiliriz? Bunun için frekans serilerindeki değerlerin histogramlarını (İng: pitch histogram) hesaplayıp inceleyebiliriz. (Histogram tanımı için bakınız. Frekans histogramlarının müzik araştırmalarında kullanımına dair bir araştırma makalesi için bakınız). İlk olarak kayıtların frekans histogramlarını çizdirip inceleyelim. Bu bize hangi frekansların sık kullanıldığına dair bilgi verecektir. Öncelikle nan değerleri dışarıda bırakıp daha sonra histogram hesaplıyor olacağız.

fig = plt.figure(figsize=(15,3))
for n, dosya in enumerate(dosyalar): 
  plt.subplot(1,len(dosyalar), n+1)
  frekans_serisi = frekans_serileri[dosya]
  frekans_serisi = frekans_serisi[~np.isnan(frekans_serisi)]
  plt.hist(frekans_serisi, bins=200);
  plt.ylabel('gözlenme sıklığı')
  plt.xlabel('frekans(Hz)')
  plt.title(dosya)
../_images/09_EntonasyonAnalizi_23_0.png

Şekil 9.4: Kayıt frekans histogramları

Bir histogram gösteriminde y ekseni kullanım sıklığını temsil etmektedir. Örneğin son kayıt olan Tanburi Cemil.mp3 dosyasında en çok 175Hz civarı frekanslar (çok dik bir tepenin olduğu nokta) icra edilmiştir.

Dağılımlardan bazı frekansların sık kullanıldığını gözleyebiliyoruz. Bunları müzisyenin zihnindeki notaların merkezi frekansları olarak düşünebiliriz. Dağılımları birbiriyle karşılaştırmaya başlamadan, daha önce de kullandığımız bir dönüşümü uygulamalıyız: frekansı logaritmik bir eksende temsil etmeliyiz. Hatırlayalım: MIDI numaralarını hesaplarken belirli frekansa oran bilgisini logaritmik olarak temsil etmiştik. 5. defterimizde kullandığımız dönüşüm fonksiyonumuz şu idi:

def hz_den_midi(frekans_hz):
  midi_no = 69 + np.log2(frekans_hz/440) * 12
  return midi_no

Histogramları hesaplamadan önce Hz cinsinden frekans değerlerini logaritmik olan MIDI değerlerine dönüştürelim ve tüm histogramları aynı frekans aralığında (MIDI = [40, 80]) çizdirelim. MIDI numarası tanım itibariyle piyanodaki klavye endekslerine denk gelen tamsayı değerlerdi ancak burada frekans uzayının logaritmik bir temsili olarak ele alacak ve ondalık sayılar kullanacağız. Bir nevi, piyanodaki tuşları çok küçük parçalara böldüğümüzü ve daha küçük adımlarla ilerlenen ve çok çok fazla sayıda tuş içeren bir piyano üzerinden bir temsil gerçekleştirdiğimizi düşünebilirsiniz.

fig = plt.figure(figsize=(15,3))
for n, dosya in enumerate(dosyalar): 
  plt.subplot(1,len(dosyalar), n+1)
  frekans_serisi = frekans_serileri[dosya]
  frekans_serisi = frekans_serisi[~np.isnan(frekans_serisi)]
  plt.hist(hz_den_midi(frekans_serisi), bins=150);
  plt.ylabel('gözlenme sıklığı')
  plt.xlabel('frekans(MIDI no)')
  plt.xlim([40,80])
  plt.title(dosya)
../_images/09_EntonasyonAnalizi_28_0.png

Şekil 9.4: Kayıt frekans histogramları (frekans logaritmik skalada (MIDI no) temsil edilmiştir)

Dağılımların frekans uzayında farklı bölgelerde olduğunu görüyoruz. Müzisyen kendi tercih ettiği frekans aralığında (Türk makam müziği terminolojisiyle ifade etmek gerekirse, üç müzisyen farklı âhenklerde) icra etmişlerdir. O nedenle, farklı icraları karşılaştırabilmek için frekans ekseninde kaydırmalar yapmamız gerekecek. Aşağıda, histogramlar arasında bir ilişki olduğunu göstermek için elle kaydırılmış sürümlerini üstüste çizdiriyoruz. Kaydırma miktarlarını makam dizisi bilgisi ve histogram şeklinden yola çıkarak belirledik.

segah_perdesi_pozisyonlari = [53, 69.3, 57.6]
fig = plt.figure(figsize=(8,8))
for n, dosya in enumerate(dosyalar): 
  plt.subplot(len(dosyalar),1, n+1)
  frekans_serisi = frekans_serileri[dosya]
  frekans_serisi = frekans_serisi[~np.isnan(frekans_serisi)]
  plt.hist(hz_den_midi(frekans_serisi)-segah_perdesi_pozisyonlari[n], bins=120, label=dosya);
  plt.ylabel('sıklık')
  plt.xlim([-2.5,12.5])
  plt.legend()
../_images/09_EntonasyonAnalizi_31_0.png

Şekil 9.5: Kaydırılmış/Transpoze edilmiş kayıt frekans histogramları (frekans logaritmik skalada (MIDI no) temsil edilmiştir)

Kaydırılmış histogramları karşılaştırdığımızda sıklıkları değişmekle beraber tepelerin yerlerinin birbirine yakın (ama farklı) yerlerde olduğunu görebiliyoruz. Bu, müzisyenlerin makam aynı olsa da farklı müzikal aralıkları tercih ettiğini gösteriyor. Şimdi frekans temsilinde MIDI numaraları kullanmak yerine yine yaygın kullanılan logaritmik temsillerden birisi olan sent (İng: cent) birimini kullanalım. Sent bir oktavlık frekans bandının logaritmik olarak 1200 eşit parçaya bölünmesi ile elde edilen birimdir. Histogramı hesaplarken de 20 sentlik aralıklar içerisindeki icra sıklıklarını hesaplayacağız. Ayrıca histogramlarımızı, 2. defterimizde tanımlayıp kullandığımız şekilde, düşük geçiren filtreden geçirerek anlık küçük varyasyonları dışarıda bırakacağız.

def dusuk_geciren_filtre(filtrelenecek_seri, uzunluk_h_n, normalize_kesim_frekansi):
  h_n = signal.firwin(uzunluk_h_n, normalize_kesim_frekansi)
  filtrelenmis_seri = np.convolve(filtrelenecek_seri, h_n)
  # Konvolusyon işlemi sonucu sinyalimiz uzadı, keselim
  filtrelenmis_seri = filtrelenmis_seri[int(uzunluk_h_n//2):int(uzunluk_h_n//2) + filtrelenecek_seri.size]
  return filtrelenmis_seri

def hz_den_sent(frekans_hz, referans_frekansi):
  return np.log2(frekans_hz / referans_frekansi) * 1200

referans_frekans = 110 # referans frekansını serideki çoğu değerden düşük rasgele bir değer olarak seçtik, fark hesaplayacağımız için önemi yok
segah_perdesi_pozisyonlari = [795,2439,1259] # gözlemle bulunan değerler
hist_aralik_adimi = 20 # histogramı 20 sent adımlarla hesaplayacağız
# Filtre tasarım parametreleri
uzunluk_h_n = 7; normalize_kesim_frekansi = 0.25
fig = plt.figure(figsize=(8,8))
for n, dosya in enumerate(dosyalar): 
  plt.subplot(len(dosyalar),1, n+1)
  frekans_serisi = frekans_serileri[dosya]
  frekans_serisi = frekans_serisi[~np.isnan(frekans_serisi)]
  # Hz-> sent dönüşüm
  frekans_sent = hz_den_sent(frekans_serisi, referans_frekans) 
  # Frekans ekseninde kaydırma
  frekans_sent -= segah_perdesi_pozisyonlari[n]
  # Histogram hesabı
  histogram_araliklari = np.arange(np.min(frekans_sent), np.max(frekans_sent)+hist_aralik_adimi, hist_aralik_adimi)
  degerler, araliklar = np.histogram(frekans_sent, bins = histogram_araliklari)
  # Histogramın küçük varyasyonlarını dışarıda bırakmak için filtreleyelim
  degerler = dusuk_geciren_filtre(degerler, uzunluk_h_n, normalize_kesim_frekansi)
  aralik_orta_noktalari = (histogram_araliklari[:-1] + histogram_araliklari[1:]) / 2
  plt.plot(aralik_orta_noktalari, degerler, label=dosya);
  plt.ylabel('sıklık')
  plt.xlim([-200,1000])
  plt.legend()
  plt.grid('minor', axis='x')
  plt.vlines(np.arange(-100,1000,100), 0, np.max(degerler), linestyle='dashed')
../_images/09_EntonasyonAnalizi_34_0.png

Şekil 9.6: Kaydırılmış/Transpoze edilmiş ve düşük geçiren filtre ile filtrelenmiş kayıt frekans histogramları (frekans logaritmik skalada (sent) temsil edilmiştir)

Histogramları çizdirirken;

  • Frekans uzayında kaydırdık (transpoze ettik)

  • Standart Batı müziği sesleriyle uyuşma düzeyini gözlemek için segah perdesini 0 noktasına denk getirerek her 100 sent’te bir dikey çizgi ekledik

Çizimlerde dikey çizgiler batı müziği eşit yedirimli standart ses sistemi nota frekanlarının yerlerini, histogramda bulunan tepeler ise müzisyenin en sık bastığı ve ilgili notayı temsil edecek frekanslar olarak düşünülebilir. Birçok noktada tepe noktaları ile dikey çizgilerin örtüşmediğini, diğer bir deyişle kullanılan müzikal aralıkların farklı olduğunu gözleyebiliyoruz.

Konu ilginizi çekti ise;

  • Bir kayıttan yola çıkarak, kayıttaki nota frekanslarını otomatik bulan, bu değerleri bir dijital klavyenin tuşlarına atayarak bilgisayarınızda bu sesleri denemenizi sağlayan bir kod örneği için MTG-MIR ders notları 4.3’e,

  • Bu işlemi birçok kayıttan yola çıkarak, kayıtların ortalama dağılımları üzerinden gerçekleştiren bir kod örneği için MTG-MIR ders notları 4.4’e bakabilirsiniz.

  • Tanburi Cemil Bey kayıtları üzerine yapılmış bazı analizlerin sonuçlarına ulaşmak için bakınız.

  • Çok sayıda kayıt üzerinden yapılan analizlerden elde edilen müzikal aralıklar ile makam müziği kuramlarının tanımladığı aralıklar arasında bir karşılaştırma için bakınız.

  • Bu defterde ve yukarıdaki içeriklerde açıklanan temel fikrin üzerine kurulu bir akort uygulaması için bakınız

Kaynakça: Bu defter hazırlanırken Barış Bozkurt tarafından daha önce hazırlanmış MTG-MIR ders notları 4.3 ve “Tutorial on Computational Approaches for Analysis of Non-Western Music Traditions”, 19th ISMIR Conference, Paris, 23/09/2018 kaynakları kullanılmıştır.

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