DALL·E 3 albümünün kapağı
Bu yazımda bir hafta sonu albümümü yayınlamak için nasıl bir proje yaptığımı paylaşacağım ( https://evhaevla.netlify.app/ ). Eğitimli bir müzisyen ya da besteci değilim ama bazen aklımda melodiler beliriyor. Bunları not alıyorum ve bilgisayarın oynatmasına izin veriyorum.
2021 yılında "Herkes Mutlu, Herkes Gülüyor" isimli albümümü çıkardım. Tanıdık olmayan bir "bestecinin" basit bir albümü; bu benim.
Sadece müzikle ilgilenmiyorum; Aynı zamanda son zamanlarda ağırlıklı olarak ön uç çalışmalarına odaklanan bir geliştiriciyim. Neden bu iki aşkı birleştirmeyelim diye düşündüm. Böylece albümümü görsel olarak sunacağım bir web sitesi tasarlamaya başladım.
Bu makale her teknik ayrıntıya derinlemesine girmeyecek; bu çok uzun olabilir ve herkesin ilgisini çekmeyebilir. Bunun yerine temel kavramları ve karşılaştığım engelleri vurgulayacağım. İlgilenenler için kodun tamamı GitHub'da bulunabilir.
Albümüm piyano için bestelendi, bu da kararı kolaylaştırıyor. Piyano tuşlarının üzerine inen dikdörtgenleri hayal edin. Müzikle ilgisi olan herkes, YouTube'da notaların bu şekilde gösterildiği çok sayıda videoyla karşılaşmış olabilir. Bir dikdörtgen bir tuşa dokunuyor, onu aydınlatıyor ve notanın vurulacağı kesin anı gösteriyor.
Bu görsel stilin kökeninden emin değilim, ancak hızlı bir Google araması ağırlıklı olarak Synthesia'nın ekran görüntülerini ortaya çıkarıyor.
YouTube'da görsel olarak çarpıcı efektler üretmeyi başaran yaratıcılar var. Bu tür videoları izlemek hem estetik hem de müzikal açıdan bir zevktir. Bunu veya şunu izle.
Neyi uygulamamız gerekecek?
Her noktayı ele alalım ve bunları eyleme geçirelim.
Başlangıçta anahtarları uygulamanın en büyük zorluğu oluşturacağını varsaydım. Ancak hızlı bir çevrimiçi arama, bunun nasıl yapılacağına dair çok sayıda örnek ve kılavuz ortaya çıkardı. Zarif bir dokunuşa sahip bir tasarım hedefleyerek Philip Zastrow'un yarattığı bir örneği tercih ettim.
Bana kalan tek şey, tuşları birkaç kez kopyalamak ve notaların kayabileceği bir ızgara oluşturmaktı. Ön uç çerçevesi olarak Vue.js'yi kullandım ve bileşen kodu aşağıdadır.
<template> <ul style="transform: translate3d(0, 0, 0)"> <li :id="`key_${OFFSET - 1}`" style="display: none"></li> <template v-for="key in keys" :key="key.number"> <li :class="`${key.color} ${key.name}`" :id="`key_${key.number}`"></li> </template> </ul> </template> <script setup lang="ts"> import { ref } from 'vue' import type { Key } from '@/components/types' const OFFSET = 24 const template = [ { color: 'white', name: 'c' // Do }, { color: 'black', name: 'cs' // Do-diez }, { color: 'white', name: 'd' // Re }, /* ... */ ] const keys = ref<Key[]>([]) for (let i = 0; i < 72; i++) { keys.value.push({ ...template[i % 12], number: i + OFFSET }) } </script>
Her tuşa, animasyonlarını başlatırken önemli olacak bir id
niteliği eklediğimi belirtmek isterim.
Bu en basit bölüm gibi görünse de, birkaç zorluk açıkça görülebilmektedir.
Azalan notaların etkisi nasıl başarılabilir?
Güncel notların alınabilmesi için sorgulanabilecek bir yapıya ihtiyaç var mı?
Bu tür sorguların sonuçlarını işlemek için en iyi yaklaşım nedir?
Her soru, istenen etkiyi sorunsuz bir şekilde elde etmek için gezinmek için bir engel sunar.
Her soru üzerinde oyalanmayacağım, bunun yerine doğrudan kovalamacaya geçeceğim. Dinamik yaklaşımla ilgili sayısız zorluk göz önüne alındığında, Occam'ın Usturası'na kulak vermek ve statik bir çözümü tercih etmek akıllıca olacaktır.
Bu konuyu şu şekilde ele aldım: 6215 notanın tümünü aynı anda tek bir geniş tuval üzerinde oluşturdum. Bu tuval overflow: hidden
özelliğiyle tasarlanmış bir kapsayıcının içinde bulunur. Düşen nota efektini elde etmek, bu konteynerin scrollTop
hareketlendirmekten ibarettir.
Ancak hala cevap bekleyen bir soru var: Her notanın koordinatlarını nasıl elde edeceğim?
Neyse ki tüm bu notaların arşivlendiği bir MIDI dosyası var, bu da albümün bestecisi olmanın getirdiği bir kolaylık. MIDI dosyasından çıkarılan verileri kullanarak notaların oluşturulmasına indirgenir.
MIDI dosyasının ikili formatta olduğu ve onu kendim ayrıştırmaya niyetim olmadığı göz önüne alındığında, midi dosyası kütüphanesinin yardımına başvurdum.
midi-file
kitaplığı, MIDI dosyalarından ham verilerin çıkarılmasında etkilidir, ancak benim ihtiyaçlarım için bu yeterli değil. Uygulama içinde kusursuz işlemeyi kolaylaştırmak için bu verileri daha erişilebilir ve uygulama dostu bir formata dönüştürmeyi hedefliyorum.
Bir MIDI dosyasında, benim uğraştığım şey alışılagelmiş anlamda notalar değil, olaylardır. Bu olayların bir dizi çeşidi var, ancak ben öncelikle iki türe odaklanıyorum: Bir tuşa basıldığında tetiklenen 'noteOn' ve tuş bırakıldığında 'noteOff'.
Hem 'noteOn' hem de 'noteOff' olayları, sırasıyla basılan veya bırakılan nota numarasını belirtir. MIDI'de geleneksel anlamda zaman yoktur. Bunun yerine "keneler" var. Vuruş başına tıklama sayısı MIDI dosyasının başlığında ayrıntılı olarak açıklanmıştır.
Aslında dikkate alınması gereken daha çok şey var. Çalma sırasında temponun değişebileceği dikkate alınarak, sürecin ayrılmaz bir parçası olan 'setTempo' olaylarını içeren bir tempo parçası da mevcuttur. İlk yaklaşımım, kabın scrollTop
özelliğinin animasyon hızını tempoyla hizalanacak şekilde ayarlamayı içeriyordu.
Ancak çok geçmeden bunun aşırı hata birikimi nedeniyle beklenen sonucu vermeyeceğini fark ettim. 'Doğrusal' bir zaman aralığının, scrollTop
canlandırmak için daha etkili olduğu kanıtlandı.
Animasyon yönü sıralanmış olsa bile temponun hâlâ dahil edilmesi gerekiyordu. Bunu notların dikdörtgenlerinin uzunluklarını ayarlayarak çözdüm. Optimum çözüm olmasa da (hızı değiştirmek ideal olurdu), bu yöntem daha sorunsuz bir çalışma sağladı.
Bu çözüm mükemmel değil, çünkü bir tempo olayını aynı veya daha az zamana sahip olmalarına bağlı olarak bir nota olayıyla ilişkilendiriyorum. Bu, bir nota hala çalınma durumundayken başka bir tempo olayı meydana gelirse, bunun göz ardı edileceği anlamına gelir.
Bu, özellikle nota çok uzunsa ve çalma süresi boyunca dramatik bir tempo değişikliği meydana geliyorsa potansiyel olarak bir hataya neden olabilir. Bu bir takas. Hızlı gelişmeye odaklandığım için bu küçük kusuru kabul ettim.
Hızın öncelikli olduğu durumlar vardır ve her ayrıntıya takılıp kalmamak daha pragmatiktir.
Yani, aşağıdaki bilgilerle donatılmış durumdayız:
Elimdeki bu ayrıntılarla her notanın tuval üzerindeki tam koordinatlarını belirleyebiliyorum. Tuş numarası X eksenini belirler, tuşa basılmanın başlangıcı ise Y eksenidir. Presin uzunluğu dikdörtgenin yüksekliğini belirler.
Standart bir div öğesi kullanarak ve konumunu 'mutlak' olarak ayarlayarak istenen etkiyi başarıyla elde ettim.
Piyano için bir sentezleyici yaratma niyetinde değildim çünkü bu çok zaman alırdı. Bunun yerine, daha önce "oluşturulmuş" mevcut bir OGG dosyasını kullandım ve ses kütüphanesi için Native Instruments'ın The Grandeur'unu seçtim.
Kişisel olarak bunun mevcut en iyi piyano VST enstrümanı olduğuna inanıyorum.
Ortaya çıkan OGG dosyasını standart bir ses öğesine gömdüm. O zaman asıl görevim, sesi not tuvalimin scrollTop
animasyonuyla senkronize etmekti.
Senkronizasyonla baş edebilmem için önce animasyonun oluşturulması gerekiyordu. Kanvas animasyonu oldukça basittir - scrollTop
doğrusal enterpolasyon kullanarak sonsuz bir değerden sıfıra kadar canlandırırım. Bu animasyonun süresi albümün uzunluğuyla eşleşiyor.
Bir tuşa nota düştüğünde o tuş yanar. Bu, her notanın inişi için ilgili anahtarı "etkinleştirmem" gerektiği ve nota seyrini tamamladığında onu devre dışı bırakmam gerektiği anlamına gelir.
Toplam 6215 notla bu, 12.430 nota etkinleştirme ve devre dışı bırakma animasyonuna eşittir.
Ek olarak kullanıcılara sesi geri sarma yeteneği sunarak albümün herhangi bir yerinde gezinmelerini sağlamayı amaçladım. Böyle bir özelliği uygulamak için sağlam bir çözüm şarttır.
Ve "sadece işe yarayan" güvenilir bir çözüme ihtiyaç duyulduğunda ilk tercihim her zaman GreenSock Animasyon Platformu olur.
Her bir tuş için tüm animasyonları oluşturmak için gereken kod miktarına bakın. Bileşenleri canlandırmak için id
kullanmak, Tek Sayfalı Uygulamalar için en iyi uygulama değildir. Ancak bu yöntem gerçek anlamda zaman tasarrufu sağlar. Her anahtar için bahsettiğim id
hatırlıyor musunuz? İşte bu noktada devreye giriyorlar.
const keysTl = gsap.timeline() notes.value.forEach((note) => { const keySelector = `#note_${note.noteNumber}` keysTl .set(keySelector, KEY_ACTIVE_STATE, note.positionSeconds) .set(keySelector, KEY_INACTIVE_STATE, note.positionSeconds + note.durationSeconds - 0.02) })
Senkronizasyon kodu esas olarak ses ile GSAP global zaman çizelgesi arasındaki olaylar aracılığıyla bir bağlantı kurar.
audioRef.value?.addEventListener('timeupdate', () => { const time = audioRef.value?.currentTime ?? 0 globalTl.time(time) }) audioRef.value?.addEventListener('play', () => { globalTl.play() }) audioRef.value?.addEventListener('playing', () => { globalTl.play() }) audioRef.value?.addEventListener('waiting', () => { globalTl.pause() }) audioRef.value?.addEventListener('pause', () => { globalTl.pause() })
Tam bitirmek üzereyken aklıma ilginç bir fikir geldi. Albüme benzersiz bir dokunuş katarsam ne olur? Başlangıçta yapılacaklar listemde yoktu ama bu özellik olmadan projenin gerçekten parlamayacağını hissettim. Ben de onu dahil etmeyi seçtim.
Ne zaman kendimi bir parçaya kaptırsam, kendimi onun daha derin anlamları üzerine düşünürken buluyorum. Besteci hangi mesajı iletmeye çalışıyordu? Örneğin Ludovico Einaudi'nin "Gece Kitabı"ndaki bir bölümü düşünün. Piyano sol kulakta yankılanırken, teller sağ kulakta yankılanıyor.
İkisi arasında gelişen bir diyalog ortamı yaratıyor. Sanki piyano tuşları şunu araştırıyormuş gibi geliyor: "Kabul ediyor musun?" Dizeler olumlu yanıt verir. "Sorgu bu mu?" Dizeler onların onayını yansıtıyor. Sekans, birlik ve uyumun gerçekleşmesini simgeleyen her iki enstrümanın bir araya gelmesiyle doruğa ulaşır. Bu büyüleyici bir deneyim değil mi?
Bunun tamamen kişisel yorumum olduğunu belirtmem gerekiyor. Bir keresinde Milano'da bir Ludovico konserine gitme fırsatım olmuştu. Gösteriden sonra ona yaklaştım ve diyalog kavramını gerçekten bu bölüme yerleştirmeyi isteyip istemediğini sordum.
Yanıtı aydınlatıcıydı: "Hiç bu şekilde düşünmemiştim ama senin kesinlikle canlı bir hayal gücün var."
Bu deneyimden yola çıkarak şunu düşündüm: Notalara altyazıları entegre etsem ne olur? Belirli bölümler çalındıkça ekranda yorumlar gerçekleşebilir ve bestecinin niyetine dair içgörüler veya yorumlar sağlanabilir.
Bu özellik, dinleyicilere "yazarın gerçekte ne demek istediği?" konusunda daha derin bir anlayış veya yeni bir bakış açısı sunabilir.
Animasyon aracım olarak GSAP'yi seçmem büyük bir şanstı. Bu, özellikle yorumu canlandırmakla görevli başka bir zaman çizelgesini zahmetsizce entegre etmeme olanak sağladı. Bu ekleme süreci kolaylaştırdı ve fikrimin uygulanmasını çok daha sorunsuz hale getirdi.
Yorumları HTML işaretlemesi yoluyla sunma eğilimindeydim. Bunu başarmak için onMounted
etkinliği sırasında animasyonu tanıtan bir bileşen hazırladım.
<template> <div :class="$style.comment" ref="commentRef"> <slot></slot> </div> </template> <script setup lang="ts"> /* ... */ onMounted(() => { if (!commentRef.value) return props.timeline .fromTo( commentRef.value, { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.5 }, props.time ? parseTime(props.time) : props.delay ? `+=${props.delay}` : '+=1' ) .to( commentRef.value, { autoAlpha: 0, duration: 0.5 }, props.time ? parseTime(props.time) + props.duration : `+=${props.duration}` ) }) </script>
Bu bileşenin kullanımı aşağıdaki gibi olacaktır.
<template> <div> <Comment time="0:01" :duration="5" :timeline="commentsTl"> <h1>A title for a track</h1> </Comment> <Comment :delay="1" :duration="13" :timeline="commentsTl"> I would like to say... </Comment>
Tüm unsurlar yerine oturduğunda bir sonraki adım siteyi barındırmaktı. Netlify'ı tercih ettim. Şimdi sizi albümü deneyimlemeye ve son sunumu izlemeye davet ediyorum.
Umarım albümlerini bu kadar benzersiz bir şekilde sergilemeye hevesli, piyano seven başka geliştiriciler de vardır. Eğer onlardan biriyseniz projeyi çatallamaktan çekinmeyin.