Javascript size piyano çalmayı öğretemez. Ama çalmayı öğrenmeniz için bir piyano(?) yapabilir. Pek muhterem okurlar, bu yazıda anlaşıldığı üzere biraz Javascript ve azıcık CSS ile basit bir piyano uygulaması yapacağız. Yazının bonusu olarak yaptığımız piyanoda Requiem for a Dream'in (Bir Rüya için Ağıt) film müziğinin nasıl çalınacağını göstereceğim.

Javascript ile Piyano Uygulaması Yapımı

Resimdeki yazıyı okuyunca Javascript sanki bizi piyano yapacakmış gibi bir anlam çıkıyor ama pek takılmamak lazım tabi. Neyse, başlayalım!

HTML Piyano ve Tuşları

Her bir nota (notanın kendisi ve diyezi) için bir li elementi olmak üzere iki oktavlık bir piyano klavyesi oluşturalım. Div elementlerini piyano tuşu olarak kullanacağız. Her bir tuş için key, beyaz tuşlar için white-key, siyah tuşlar için black-key sınıfını ekleyelim. Şimdilik aşağıdaki gibi.

<div class="piano">
     <ul>
        <li>
            <div class="key white-key" data-tone="do" data-octave="4" data-shortcut="Z"></div>
            <div class="key black-key" data-tone="dodiyez" data-octave="4" data-shortcut="S"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="re" data-octave="4" data-shortcut="X"></div>
            <div class="key black-key" data-tone="rediyez" data-octave="4" data-shortcut="D"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="mi" data-octave="4" data-shortcut="C"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="fa" data-octave="4" data-shortcut="V"></div>
            <div class="key black-key" data-tone="fadiyez" data-octave="4" data-shortcut="G"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="sol" data-octave="4" data-shortcut="B"></div>
            <div class="key black-key" data-tone="soldiyez" data-octave="4" data-shortcut="H"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="la" data-octave="4" data-shortcut="N"></div>
            <div class="key black-key" data-tone="ladiyez" data-octave="4" data-shortcut="J"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="si" data-octave="4" data-shortcut="M"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="do" data-octave="5" data-shortcut="Q"></div>
            <div class="key black-key" data-tone="dodiyez" data-octave="5" data-shortcut="2"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="re" data-octave="5" data-shortcut="W"></div>
            <div class="key black-key" data-tone="rediyez" data-octave="5" data-shortcut="3"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="mi" data-octave="5" data-shortcut="E"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="fa" data-octave="5" data-shortcut="R"></div>
            <div class="key black-key" data-tone="fadiyez" data-octave="5" data-shortcut="5"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="sol" data-octave="5" data-shortcut="T"></div>
            <div class="key black-key" data-tone="soldiyez" data-octave="5" data-shortcut="6"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="la" data-octave="5" data-shortcut="Y"></div>
            <div class="key black-key" data-tone="ladiyez" data-octave="5" data-shortcut="7"></div>
        </li>
        <li>
            <div class="key white-key" data-tone="si" data-octave="5" data-shortcut="U"></div>
        </li>
    </ul>
</div>

Gördüğünüz üzere divlerde üç tane data özelliği var. Basılan tuşun notasını almak için data-tone, oktavını almak için data-octave ve klavye tuşunu belirlemek için data-shortcut kullanacağız.

Bu haliyle pek hoş görünmediği aşikar. Şimdi biraz şekillendirelim.

Biraz Piyanoya Benzeyeydi...

Önce tüm tuşlar için ortak özellikleri tanımlayalım. Ben bir de arkaplan rengi ekledim, dilerseniz eklemeyebilirsiniz.

body{
    background: #F2F1EF;
}

.piano ul li {
    float: left;
    position: relative;
    border: 1px solid #f8f1e5;
    list-style-type: none;
    box-sizing: border-box;
}

Tuşların yan yana durması için float, madde işaretlerinin görünmemesi için list-style-type ve tuşların genişliğinin sabit olması için box-sizing özelliklerini ekledik. Kenarlık rengini belirleyip siyah tuşların beyazların üzerinde durabilmesi için position özelliğini relative yaptık.

Şimdi beyaz tuşların özelliklerini belirleyelim.

.piano ul li .white-key {
    cursor: pointer;
    background: #faf5ed;
    width: 56px;
    height: 140px;
    box-shadow: #575757 0px 3px 3px -1px;
}

.piano ul li .white-key:active {
    background: #f6ecdd;
    box-shadow: gray 0px 1px 3px -1px;
}

Arkaplan, genişlik ve yükseklik değerlerini verdik. Cursor özelliği ile fare işaretçini pointer (linkin üzerine geldiğinde oluşan el) yaptık ve tuşun altına bir gölge ekledik.

Tuşa tıklandığında (:active) arkaplan rengini ve gölgesini düzenledik. Bu sayede tuşa basılmış hissi uyandıracak bir görünüm yakaladık.

Sıra geldi siyah tuşlara.

.piano ul li .black-key {
    cursor: pointer;
    background: #262626;
    display: block;
    position: absolute;
    top: 0;
    right: -20%;
    z-index: 2;
    height: 84px;
    width: 22.4px;
    box-shadow: black 0px 3px 3px 0px;
}

.piano ul li .black-key:active {
    background: #191919;
    box-shadow: black 0px 2px 2px 0px;
}

Öncekinden farklı olarak, tuş konumunu ayarlayabilmek için position özelliğine "absolute" verdik. Top ve Right ile tuşun yukarıya yapışık olmasını, sağa doğru -20% kaymasını sağladık. Z-index, katman özelliğidir, 2 vererek siyah tuşların beyazların üzerinde görünebilmesini sağladık. Ve beyaz tuşlarda olduğu gibi siyahlar için de :active özelliklerini verdik.

Evet şimdi piyanoya benzedi değil mi? Ama bunu ben yazmadım, sadece bu yazının seviyesine göre biraz düzenledim, orjinalini şuradan görebilirsiniz. Ayrıca incelemek isterseniz şöyle de bir şey var ki, akıllara zarar.

Veysel efendi piyanomuz neden çalışmıyor?

Önce sizin için hazırladığım piyano seslerini şuradan indirin. Zip dosyasını projenizin içinde çıkartın.

Şimdi de biraz javascript yazalım.

var keys, keydown = {};

function onBodyLoad(){
    keys = document.querySelectorAll(".key");

    // Tuşlar için Click Olayı
    for(var i = 0; i < keys.length; i++){
        keys[i].onmousedown = function (event){
            var tone = this.dataset.tone;
            var octave = this.dataset.octave;

            new Audio("PianoKeys/" + tone + octave + ".wav").play();
        };
    }

    document.addEventListener("keydown", onKeyDown, true);
    document.addEventListener("keyup", onKeyUp, true);
}

onBodyLoad fonksiyonunun sayfa yüklendiğinde çalışması için body etiketine onload="onBodyLoad()" özelliği vermeyi unutmayın. Tanımdığımız değişkenlerin biri tuşları (keys), diğeri basılı olan tuşları (keydown) hafızada tutmak için.

Önce querySelectorAll fonksiyonu ile "key" sıfına ait tüm elementleri "keys" dizisinde topladık. Ardından bir döngü ile topladığımız tüm elementlere onMouseDown (fare ile tıklandığında) eventi tanımladık.

Event fonksiyonu içinde basılan tuşun notasını (this.dataset.tone) ve oktavını (this.dataset.octave) aldık. hatırladığınız üzere bu özellikleri html kısmında tanımlamıştık. Dataset nesnesi HTML elementlerine verilen data-* özelliklerini okumaya yarar.

Hemen altında, yeni bir Audio nesnesi oluşturup, play fonksiyonu ile oynattık. Mesela gelen nota do diyez, oktav 5 olsun. Çaldıracağımız wav dosyasının adı "PianoKeys/dodiyez5.wav" olacaktır. Az önce indirdiğiniz dosyanın içinde kullanacağımız tüm sesler var.

Neden değişken tanımlayıp da kullanmadık? Normalde bir ses oynatacaksanız yazacağınız kodlar şunlardır:

Audio sesNesnesi = new Audio("dosya.wav");
sesNesnesi.play();

Ben de ilk başta buna benzer bir şeyler yaptım, ancak bir tuşa iki defa ard arda basıldığında sesi oynatmıyor, bitmesini bekliyordu. O yüzden oynatmadan önce currentTime özelliğini sıfır yaptım. Böyle olunca da hızlı hızlı basıldığında seste kesiklik oluşuyordu.

Sorunun merkezi, tek bir tane Audio nesnesi kullanmaktı. Bizim kullandığımız şekilde yaptığımızda isimsiz bir nesne o anda üretilip kullanılıyor. Bir sonraki basışta yeni bir nesne üretiliyor. Zaten bir kere çaldığımız ses nesnesiyle bir daha işimiz olmayacağı için bizim için en mantıklı yol bu.

Buraya kadar yazdığımız kodları denerseniz çalıştığını göreceksiniz. Ancak dünyanın hiç bir yerinde fare ile piyano çalınmadığı için biz de piyanomuzun klavye ile çalınabilmesini sağlayalım.

Yazdığımız kodların alt kısmında addEventListener fonksiyonu ile sayfaya genel bir onKeyDown (tuşa basıldığında) ve onKeyUp (tuş bırakıldığında) eventi ekledik. Şimdi bu eventler için fonksiyonlar yazalım.

function onKeyDown(event){
    var pressedKey = String.fromCharCode(event.charCode || event.keyCode);

    if(!keydown[pressedKey]){
        for(var i = 0; i < keys.length; i++){
            if(keys[i].dataset.shortcut == pressedKey){
                keys[i].onmousedown();
                keydown[pressedKey] = true;

                break;
            }
        }
    }
}

Önce onKeyDown fonksiyonuna bakalım. Parametre olarak gelen event nesnesinin keyCode code özelliği basılan tuşun ASCII kodunu verir. Örneğin A tuşuna basarsak fonksiyon bize 65 kodunu döndürür. Diğer kodlara bakmak için şuraya bakabilirsiniz.

Internet Explorer bu kodu charCode içinde, diğer tarayıcılar keyCode içinde döndürür. Piyanonun her iki ihtimalde de çalışması için ikisini de aldık. String.fromCharCode fonksiyonu ile karaktere çevirdik. Böylece basılan tuşu pressedKey değişkeninde görebileceğiz.

Hemen altında bir if bloğu görüyorsunuz. Anlamı şu; keydown dizisindeki pressedKey elemanı (basılan tuş neyse, o eleman) içindeki değer true değilse. Yani o tuşa önceden basılmamışsa kod bloğunu çalıştır.

Altındaki for döngüsü ile onBodyLoad eventinde topladığımız ".key" elemenlerini tek tek dolaşacak ve data-shortcut özelliği basılan tuşa (pressedKey) eşit olan tuşun tıklanma (onMouseDown) eventini çalıştıracak. Basılan tuşu keydown dizisine true değeriyle ekleyecek. Son olarak döngünün artık dolaşmasına gerek kalmadığı için break ile döngüden çıkacak.

Basılan tuş serbest bırakıldığında (onKeyUp) basitçe keydown dizisi içindeki değerini false yapacağız. Bu sayede basılan tuştan el çekilene kadar ses defalarca oynatılmayacak.

function onKeyUp(event){
    var pressedKey = String.fromCharCode(event.charCode || event.keyCode);
    keydown[pressedKey] = false;
}

El yapımı piyanonuz kullanıma hazır!

İşte bu kadar. Bitti. Başardık, bay Frodo!

Her ne kadar çok da efektif bir piyano olmadıysa da, en azından sırf tuşlarını yapmak için filleri öldürmedik. Güle güle kullanın...

Javascript ile Piyano Uygulaması Yapımı
Piyanomuzun son hali böyle görünüyor.

Bonus: Requiem for a Dream

Sıra geldi bonusa. Bu parçayı çalabilmek için piyanomuza bir tuş daha eklememiz gerekiyor. HTML tuşlarına (en sona) aşağıdaki satırı ekleyin ve buradan yeni tuş için gerekli sesi indirin.

        <li>
            <div class="key white-key" data-tone="do" data-octave="6" data-shortcut="I"></div>
        </li>

Nasıl çalınır?

Aşağıda sırası ile basmanız gereken tuşları verdim. Üstteki tuşları sağ elle, alttakileri sol elle çalmanızı tavsiye ederim. Tabi ki alt ve üst taraflar aynı anda çalınmalı. Tire (-) olan yerler bir şeye basmayın demek. Düz çizgiler (|) ölçü bitimini gösteriyor. Dördüncü ölçü sonuna geldiğinizde başa dönmelisiniz.

     {1}       {2}       {3}       {4}      
    -----------------------------------------
SAĞ | 7 Y T W | 7 Y T W | 7 Y T W | I 7 Y 7 |
SOL | B - - - | - - - - | D - - - | X - - - |

Tabi ki bu parçanın kısa bir bölümünün en basit hali. Daha da geliştirip çalınabilir. Piyanoya farklı enstrümanlar eklenip düğün müzikleri bile yapılabilir. :)

Dilerseniz, bir yandan requiem for a dream çalarken diğer yandan bu yazıyı paylaşmanın zevkine varabilirsiniz: