oniki maymun

Le Montage

Uğur Işılak'ın Koalisyon videosunu ilk izlediğimde herkes gibi ben de çok etkilendim. Tüm programımı iptal edip defalarca dinledikten sonra böyle bir sanatçının mecliste milletvekili olarak temsil edilmesinden bir kez daha kıvanç duydum. Her zaman söylemişimdir sanat siyaset içindir diye. Zaman beni haklı çıkardı.

Ancak, üzülerek söylüyorum, toplum olarak çok çabuk tüketiyoruz herşeyi. Sanatla geçen bir ömrün eseri olan bu çalışma, aradan bir hafta bile geçmeden neredeyse unutuldu. Oysa bu şiirin her mısrası, içinde bulunduğumuz konjönktür dikkate alınarak tekrar tekrar dinlemlenmeliydi.

Toplum olarak bir başka özelliğimiz de kasettir, montajdır böyle şeyleri seviyor olmamız. Altın çağını geride bırakmış olsa da, bu konuda kitlesel bir talep olduğu uzmanlar tarafından sık sık dile getiriliyor. İşte ben de bu talebi karşılamak ve ülkeme daha faydalı bir vatandaş olmak amacıyla elimi taşın altına soktum ve ortaya Le Montage ismini verdiğim uygulama çıktı. Batı özentiliği olarak anlaşılmasın. Sanatı kendi tekelinde zanneden Cihangir sakinlerine bir göndermedir Le Montage ismi.

Hemen kurcalamak isteyenler uygulamaya buradan erişebilir. Nasıl yapılmış saatine kalmak isteyenler okumaya devam etsin.


Yazıyı buraya kadar goygoyla getirdim ama buradan sonrasına first person shooter olarak devam edeceğim...

Amacım şiirin her mısrasını ayrı bir ses dosyası olarak kaydedip, bu parçaları istediğim sırayla dinleyebileceğim bir player yapmak. İş planım şu şekilde:

  1. Ses dosyasını parçalara ayır.
  2. Bir şekilde, browser'da çalışacak bir audio player yaz.
  3. Profit!!!!

1. Ses dosyasını parçalara ayır

Wikipedia/Stack Overflow ikilisi sağolsun; Bir konuşmadaki sesli/sessiz kısımları algılama işine genel olarak "Voice Activity Detection" dendiğini öğreniyorum. 1,2,3.

Yazılanlara göre, minimumda yapılması gerekenler:

  1. Ses örneklerinin mutlak değerlerini almak
  2. Hesaplanmış mutlak değerlere lowpass filtre uygulamak
  3. lowpass filtre uygulanmış örneklere threshold uygulamak
  4. threshold değerini yeterince uzun bir süre geçen kısımları sesli kısım olarak etiketlemek.

veya hazıra konmak. İkisini de yapacağım.

Öncelikle elimde bir ses dosyası olması lazım. Ses dosyasına giden yolda videoyu indirmek ilk adım ve youtube-dl bu iş için var:

youtube-dl -o koalisyon.avi https://www.youtube.com/watch?v=NQ6xzrjmGN8 

Videodan ses ayıklama işini de ffmpeg güzel yapıyor:

ffmpeg -i koalisyon.avi koalisyon.wav

22MB boyutunda 44100Hz, Stereo, 16bit PCM bir WAV dosyam oldu. Bu dosyadan istediğim şekilde parça ayıklayabileceğimden emin olamadığımdan, öncelikle hazıra konma yöntemini deneyeceğim.

1.1 Hazıra konma yöntemi

Bunun için açık kaynak kodlu ses işleme programı Audacity'nin Silence Finder özelliğini kullanacağım. Audacity'nin böyle bir özelliği olduğunu rüyamda görmüştüm. Öncelikle Audacity'i çalıştırıp wav dosyasını açmak gerekli doğal olarak. İlk 15 saniyelik kısmın dalga formu aşağıdaki gibi. Sessiz kısımlar bariz şekilde görülüyor.

Analyze menüsünden Silence Finder seçtikten sonra, vahiy yoluyla tarafıma iletilen değerleri girerek OK butonuna basıyorum:

Bana vahiy gelmedi diyenler, grafikten sessiz kısımların ne uzunlukta olduğuna göz kararı bakıp değer girebilir. İşlem tamamlanınca sessiz kısımların başlangıç (ve bitişleri) işaretlenmiş olarak karşımızda.

File->Export Multiple komutu ve parça sayısı kadar OK butonu basımından sonra tüm parçaları mp3 olarak yazdırmış oluyorum (mp3 yazdırabilmek için lame kütüphanesi gerekli).

Yazılan dosyaları birkaçını dinleyip bu iş tamam diyorum. Artık kendimden emin bir şekilde, parçalama işi için kod yazmaya başlayabilirim (şu noktada tamamen gereksiz boş bir iş).

1.2 Puroğramcılık

Ses verisine erişmek ve işlemek için NAudio kütüphanesini kullanacağım. Kendisi Nuget paketi olarak mevcut ve projeye ekleyerek ilk adımı atıyorum. Yapılması gerekenleri yukarıda sıralamıştım ama sözelciler için bir kez daha tekrarlayayım:

  1. Ses örneklerinin mutlak değerlerini al
  2. Hesaplanmış mutlak değerlere 10 Hz civarında lowpass filtre uygula
  3. lowpass filtre uygulanmış örneklere threshold uygula
  4. threshold değerini yeterince uzun bir süre geçen kısımları ayıkla.

İlk iki adıma zarf(envelope) hesaplama deniyor(muş). Dosya stereo, dolayısıyla sol ve sağ kanal için örnekler ardarda. Uğur Işılak her mikrofona eşit mesafede duran bir sanatçı olduğundan, iki kanal da birbirinin aynısı neredeyse. O yüzden sadece sol kanalı işlemek yeterli olacak.


    // dosyayi ac
    using(WaveFileReader reader=new WaveFileReader(@"koalisyon.wav"))
    {
        // wav formatinin detaylariyla degil icerigiyle ilgilendigim icin SampleProvider kullaniyorum.
        ISampleProvider provider = new Pcm16BitToSampleProvider(reader);

    // lowpass filtre: 10Hz
        BiQuadFilter lpFilter =BiQuadFilter.LowPassFilter(provider.WaveFormat.SampleRate, 10.0f, 1.0f);

    // veriyi bloklar halinde okumak gerekiyor.
        float[] buffer = new float[4096];

    // islenmis veri buraya
        float[] outData = new float[reader.SampleCount];

        // Okumaya basla
        int rc; int k = 0;
        while ((rc = provider.Read(buffer, 0, blockSize)) > 0)
        {
        // sol kanal icin iki atlayarak islemek lazim.
            for (int j = 0; j < rc; j += 2)
            {
              // Mutlak degerlere lowpass filtre uygula
                outData[k++] = lpFilter.Transform(Math.Abs(buffer[j]));
            }
        }
    // outData: sol kanalin zarf verisini iceriyor.
    }

outData'yı wav olarak yazdırıp bakınca dalga formunun aşağıdaki hale geldiğini görüyor ve hiç şaşırmıyorum.

Bu resimdeki düzlükleri bulup işaretlemek lazım. Üçüncü ve dördüncü adımlar yani. Belirli bir eşik(threshold) değerinden küçük değerlerin sayısı (min. sessizlik*örnek sayısı) kadar olduğunda sessiz kısmı buldum demektir. Esas mesele konuşma esnasındaki küçük sessizlikleri dikkate almamak. Aşağıdaki finite state machine çakması kısım bu işi yapıyor.

float ThresholdLevel = 0.008f; //deneme yanılma
float MinSilenceLength = 0.15f; // saniye cinsinden

...

public int[] Process(float[] buffer)
{
    List<int> segments = new List<int>();
    int minSilentSampleCount = (int)(SamplingRate * MinSilenceLength);
    int i = 0, len = 0, pos = 0, startPos = 0, endPos = 0;
    bool inSpeech = buffer[0]>=ThresholdLevel; 
    bool scanSpeech = inSpeech;
    while(i<buffer.Length)
    {
        pos = i;
        len = skipSegment(buffer, ref i, scanSpeech);
        if (scanSpeech && inSpeech)
        {
            scanSpeech = !scanSpeech;
        }
        else if (!scanSpeech && inSpeech && len < minSilentSampleCount)
        {
            scanSpeech = !scanSpeech;
        }
        else if (!scanSpeech && inSpeech && len >= minSilentSampleCount)
        {
            endPos = pos;
            scanSpeech = !scanSpeech;
            segments.Add(startPos);
            segments.Add(endPos);
            inSpeech = true;
            startPos = i;
        }
        else if (!scanSpeech && !inSpeech && len >= minSilentSampleCount)
        {
            startPos = i;
            inSpeech = true;
            scanSpeech = true;
        }
    }
    return segments.ToArray();
}
private int skipSegment(float[] buffer,ref int pos,bool scanSpeech)
{
    int len = 0;

    if (!scanSpeech)
        while (pos < buffer.Length && buffer[pos++] < ThresholdLevel) len++; //skip silence
    else
        while (pos < buffer.Length && buffer[pos++] >= ThresholdLevel) len++; //skip speech

    return len;
}

Bu işlemin başarısı verilen sessizlik süresi ve threshold değerine oldukça bağlı. Düzgün sonuç veren değerleri bulana kadar biraz uğraşmadım desem yalan olur. Tüm mısraları hatasız bir şekilde ayıklamak mümkün olmadı. Audacity ile de mümkün olmamıştı. Farklı değerlerle bir kaç kez çalıştırıp, oluşan parçaları daha sonra ayıklamak gerekiyor. Mükemmeli yakalamak için tek yol machine learning. Onun için de etiketlenmiş veri gerekli ki, sözkonusu dil Türkçe olunca, orada duruyoruz. Çok canım sıkılırsa belki ileride tekrar bulaşırım bu meseleye.

Bu noktada elimde her mısra için bir mp3 dosyası var. Artık parçaları mediaplayer'a atıp istediğim sırayla dinleyerek iki dakika içinde sıkılabilirim. Ama bu amaçla yazılmış bir player olsa daha hızlı, daha verimli sıkılabilirim diyerek iş planımın ikinci maddesine geçiyorum.

2. Browser tabanlı audio player yaz

Buradan sonrası javascript ve CSS ile imtihanım şeklinde çünkü bu ikisini öğrenmekten bugüne kadar aktif bir şekilde kaçındım. Web işleri ile son uğraştığımda insanlar tablolarla resim hizalamaya çalışıyor, biraz kasıntı durumlar varsa da Flash ile hallediyorlardı. Yine herkesin kendisi ve de yeğeni web sitesi işindeydi, o tarafta değişen birşey olmamış. Fiyatlar 10TL değildi sanırım. Neyse, en azından dibe doğru yarış sonlanmış gözüküyor.

Bakına bakına CSS tarafında bootstrap'te karar kılıyorum. Twitter yazmış, örnekler sayfası güzel duruyor. Ama önce fonksiyonellik (javascript). Sürükle bırak işini javascript kullanarak nasıl yaparım sorusunun cevabı çoğunlukla 'jQuery kullanarak' diye verilmiş olduğundan jQuery ile devam etme kararı alıyor ve Stack Overflow'dan aldığım güçle, Allah rızası için dalıyorum. İlk versiyon fonksiyonel ama görsel olarak bokuma benziyor. CSS tarafına geçmek istiyorum ama daha önemli bir sorun var. Parça geçişlerinde arada oldukça rahatsız edici boşluklar oluyor. Böyle montaj olmaz.

Aynı şeyleri Audacity'de dinleyince boşluklar farkedilmiyor. Hmmm. Gene Google.

Yapılması gereken şeye crossfading deniyormuş. Buyur hadi bakalım. O anda çalan parça bitmeden yenisine başlasam olur bence, diye yola çıkarak HTMLAudioElement'in timeupdate event'inde aşağıdaki taklayı ataraktan amacıma ulaşıyorum. Yine yeri gelmişken sözelciler için bir hatırlatma yapayım: Gerçek crossfading bu değil.

    var delta = 300; //in milliseconds
    var currentTimeMs = playList[currentTrack].currentTime * 1000;
    var totalTimeMs = playList[currentTrack].duration * 1000;

    // crossfading
    if (currentTimeMs + delta > totalTimeMs && (currentTrack < playList.length - 1)) 
    {
        playNextTrack();
    }

Evet, artık geçişler daha pürüzsüz. A Haber kalitesinde. Artık işin görsellik tarafına geçebilirim ve görsellik tarafında tek diyebileceğim şey 'bootstrap ile iyi vakit geçirdim' olacak. Tırı vırı (trivial) şeyler olduğu için açıklayıp beyninizi çöple doldurmaya gerek görmüyorum.

3. Profit!!!

Geldik belki de en önemli kısıma. Gün geçmiyor ki internet aleminde getirisi bariz sıfır olacak boş beleş işler yapılmasın. İşte bu da onlardan biri. İçinde bununla beraber sadece iki yazı olan blog'uma, Google Analytics verilerine göre, bugüne kadar sadece ben girmişim. Bu ikinci yazı sayesinde hem içeriği, hem de okuyucu sayısını %100 arttırıp, sayfanın orta yerine de kocaman bir bahis reklamı koyarsam 10^-8 cent kazanabilirim bence. Büyük hissediyorum.

Neyse, bir sonuç değerlendirmesi yapacak olursam, ortaya çıkan şeyi yaklaşık 2 dakika kurcaladıktan sonra sıkıldım. Sonra gidip Van mitingi videosuna aynı işlemleri uyguladım da kendime geldim. Sırada püskevit ve 40. yıl var. Vakit bulursam hepsinin bir arada kurcalanabildiği birşeyler yapmaya çalışacağım ama o zamana kadar:

Oynatalım Uğur'cum.