Vue.js + Tone.js 開發Web鋼琴應用

原文連接css

最近用Vue + Tone.js作了一款鋼琴類web應用,名字定爲自由鋼琴(AutoPiano),人生如音樂,歡快且自由。 此文權看成該項目的總結和分享~html

項目簡介

自由鋼琴(AutoPiano)是利用HTML5技術開發的在線鋼琴應用,致力於爲鋼琴愛好者、音樂愛好者以及其餘全部的創造者提供一個優雅、簡潔的平臺,在學習工做之餘能夠享受鋼琴、音樂的美好。就相似於多年前Flash開發的鋼琴遊戲,自由鋼琴只是換了H5的技術,同時支持了鋼琴曲的自動播放功能。vue

AutoPiano支持鍵盤按鍵和鼠標點擊播放,同時琴鍵上會有按鍵和音名提示。另外,AutoPiano還有教學的功能,一種方式是快速入門,經過簡易的譜子按鍵進行演奏,另外一種是演奏示例,經過鋼琴曲的自動播放來達到演示的目的。目前這兩個功能都在持續完善中,以下圖所示:ios

autopiano.png

體驗地址: www.autopiano.cngit

開發這樣的應用須要樂理知識嗎?

固然。基本的樂理知識仍是要知道的,好比 CDEFGAB 音名、五線譜、調式、節奏等等仍是要懂一點的。篇幅所限,這裏就不展開討論了,推薦兩個網站:github

其餘的就是編程知識了,以及如何將樂理知識轉化爲程序邏輯。AutoPiano目前採用的技術架構是vue框架 + tone.js。web

鋼琴界面效果是怎麼寫的?

能夠用CSS或貼圖。筆者這裏直接用css實現了,考慮到鋼琴有黑鍵和白鍵,且黑鍵和白鍵有序地排列成 7:5的模式,因此實現起來並不複雜。編程

<div class="piano-key-wrap">
  <div class="piano-key wkey" v-for="note in Notes" :key="note.keyCode" :data-keyCode = "note.keyCode" v-if="note.type=='white'" @click="clickPianoKey($event, note.keyCode)"></div>
  <div class="bkey-wrap bkey-wrap1">
    <div class="piano-key bkey" v-for="note in Notes" :key="note.keyCode" :data-keyCode = "note.keyCode" v-if="note.type=='black' && note.id >= 36 && note.id <= 40" @click="clickPianoKey($event, note.keyCode)"></div>
  </div>
</div>
複製代碼
.piano-wrap { width: 90%; margin: 20px auto;
  .piano-key-wrap {
    width: 100%;
    background: @dark;
    overflow: hidden;
    position: relative;
    .wkey {
      display: inline-block;
      width: 2.775%;
      height: 100%;
      margin: 0 auto;
      background: linear-gradient(white 10%, rgb(251, 251, 251) 92%, rgb(220, 220, 220) 93%, white 97%);
      border: solid 1px @dark;
      border-radius: 0 0 5px 5px;
      position: relative;
      &:active {
        background: linear-gradient(#eee 10%, #ddd 60%, #bbb 93%, #ccc 97%);
      }
    }
    .wkey-active {
      background: linear-gradient(#eee 10%, #ddd 60%, #bbb 93%, #ccc 97%);
    }
    .bkey-wrap {
      width: 20%;
      height: 0;
      position: absolute;
      top: 0;
    }
    .bkey-wrap1 {left: 0;}
    .bkey-wrap2 {left: 19.5%;}
    .bkey-wrap3 {left: 39%;}
    .bkey-wrap4 {left: 58.3%;}
    .bkey-wrap5 {left: 77.7%;}
    .bkey {
      display: inline-block;
      width: 10%;
      height: 70%;
      background: linear-gradient(#000 10%, rgb(86, 86, 86) 85%, #000 90%);
      border-radius: 0 0 3px 3px;
      position: absolute;
      top: 0;
      overflow: hidden;
      &:active {
        background: linear-gradient(rgb(86, 86, 86) 10%, #000 90%, #222 100%);
      }
    }
    .bkey-active {
      background: linear-gradient(rgb(86, 86, 86) 10%, #000 90%, #222 100%);
    }
    .bkey:nth-child(1) {left: 9%;}
    .bkey:nth-child(2) {left: 23%;}
    .bkey:nth-child(3) {left: 50%;}
    .bkey:nth-child(4) {left: 65%;}
    .bkey:nth-child(5) {left: 79%;}
  }
}
複製代碼

codepen上也有不少這樣的例子供參考,不必定採用上述實現:bash

codepen.io/search/pens…架構

相信只要合理地控制css變量和數值,你們能作出更好的 Piano 界面。

如何實現單個音符的播放?

實現音頻播放,最簡單的就是利用HTML5 中的 audio 標籤,經過觸發audio的play和pause方法,實現對音頻的控制,筆者一開始就是這麼實現的。

// <div class="audios-wrap" id="audios-wrap">
// <audio src="" id="preloadAudio" ref="preloadAudio"></audio>
// </div>

// 預先爲每一個音符都創建一個audio元素
initAudioDom() {
  var vm = this
  for (let i = 0; i< vm.Notes.length; i++) {
    var note = vm.Notes[i]
    $('.audios-wrap').append(`<audio src='${note.url}' hidden='true' data-id='audio${i}' class='audioEle'>`);
  }
},
// 觸發某個audio元素的播放
playNote(url) {
  var vm = this
  if (!url || typeof url != 'string') return;
  var audios = $('.audioEle');
  for (let i = 0; i< audios.length; i++) {
    let audio = audios[i];
    if (audio.src.indexOf(url) > -1) {
      var cloneAudioNode = audio.cloneNode()
      cloneAudioNode.play()
      cloneAudioNode.remove()
      break;
    }
  }
}
複製代碼

上述是個人第一種實現方式,即不一樣音符觸發不一樣audio的播放。以後也許是出於好奇,嘗試了 Tone.js,經過Tone.js + 內置採樣器實現對音頻播放更有效的控制,固然,其提供的不少複雜功能都還沒用上。。。

// 初始化合成器
this.synth = SmapleLibrary.load({
  instruments: "piano"
}).toMaster()

// 合成器觸發音頻釋放
playNote(notename = 'C4', duration = '2n') {
  if (!this.synth) return
  this.synth.triggerAttackRelease(notename, duration);
}
複製代碼

嗯,如今的代碼就符合音樂美學和代碼美學了,美滋滋。固然筆者也指望Tone.js能快點完善中文文檔,否則上手仍是很吃力的,感興趣的小夥伴能夠先去其官網研究一番。

關於鋼琴曲的自動播放

這一部分應該是開發整個應用最難的地方了,由於音樂或者說曲譜自己是至關複雜的,根據百度百科的描述,五線譜起源於希臘,歷經上千年不斷完善才成爲如今的曲譜標準。而簡譜的出現則要晚的多,但依然五臟俱全,能夠說,簡譜也不簡單。

筆者的實現思路是,以一種曲譜格式爲載體,將曲譜轉換爲一種程序可識別的格式,而後導入到程序中進行播放,這種可識別格式以下所示,也是目前所採用的:

{
    name: '小星星',
    step: 'C',
    speed: '100',
    playState: '',
    mainTrack: ['1(1)',' 1(1)',' 5(1)',' 5(1)',' 6(1)',' 6(1)',' 5(2)',' 4(1)',' 4(1)',' 3(1)',' 3(1)',' 2(1)',' 2(1)',' 1(2)',' 5(1)',' 5(1)',' 4(1)',' 4(1)',' 3(1)',' 3(1)',' 2(2)',' 5(1)',' 5(1)',' 4(1)',' 4(1)',' 3(1)',' 3(1)',' 2(2)',' 1(1)',' 1(1)',' 5(1)',' 5(1)',' 6(1)',' 6(1)',' 5(2)',' 4(1)',' 4(1)',' 3(1)',' 3(1)',' 2(1)',' 2(1)',' 1(2)',
                '1<(1)', '1<(1)', '5<(1)', '5<(1)', '6<(1)', '6<(1)', '5<(2)', '4<(1)', '4<(1)', '3<(1)', '3<(1)', '2<(1)', '2<(1)', '1<(2)', '5<(1)', '5<(1)', '4<(1)', '4<(1)', '3<(1)', '3<(1)', '2<(2)', '5<(1)', '5<(1)', '4<(1)', '4<(1)', '3<(1)', '3<(1)', '2<(2)', '1<(1)', '1<(1)', '5<(1)', '5<(1)', '6<(1)', '6<(1)', '5<(2)', '4<(1)', '4<(1)', '3<(1)', '3<(1)', '2<(1)', '2<(1)', '1<(2)'],
    backingTrack: ['1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)', '1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)', '1>(0.5)', '6>(0.5)', '4>(0.5)', '6>(0.5)', '1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)', '1>(0.5)', '6>(0.5)', '4>(0.5)', '6>(0.5)', '1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)', '7>>(0.5)', '5>(0.5)', '2>(0.5)', '5>(0.5)', '1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)', '1>(0.5)', '3>(0.5)', '5>(0.5)',' 1(0.5)', '1>(0.5)', '4>(0.5)', '6>(0.5)',' 1(0.5)', '1>(0.5)', '3>(0.5)', '5>(0.5)',' 1(0.5)', '5>>(0.5)', '7>>(0.5)', '2>(0.5)', '5>(0.5)',
                  '1>(0.5)', '3>(0.5)', '5>(0.5)',' 1(0.5)', '1>(0.5)', '4>(0.5)', '6>(0.5)',' 1(0.5)', '1>(0.5)', '3>(0.5)', '5>(0.5)',' 1(0.5)', '5>>(0.5)', '7>>(0.5)', '2>(0.5)', '5>(0.5)', '1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)', '1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)', '1>(0.5)', '6>(0.5)', '4>(0.5)', '6>(0.5)', '1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)', '1>(0.5)', '6>(0.5)', '4>(0.5)', '6>(0.5)', '1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)', '7>>(0.5)', '5>(0.5)', '2>(0.5)', '5>(0.5)', '1>(0.5)', '5>(0.5)', '3>(0.5)', '5>(0.5)',

                  '1(0.75)', '5(0.25)', '3(0.5)', '5(0.5)', '1(0.75)', '5(0.25)', '3(0.5)', '5(0.5)', '1(0.75)', '6(0.25)', '4(0.5)', '6(0.5)', '1(0.75)', '5(0.25)', '3(0.5)', '5(0.5)', '1(0.75)', '6(0.25)', '4(0.5)', '6(0.5)', '1(0.75)', '5(0.25)', '3(0.5)', '5(0.5)', '7>(0.75)', '5(0.25)', '2(0.5)', '5(0.5)', '1(0.75)', '5(0.25)', '3(0.5)', '5(0.5)', '1(0.75)', '3(0.25)', '5(0.5)', '1<(0.5)', '1(0.75)', '4(0.25)', '6(0.5)', '1<(0.5)', '1(0.75)', '3(0.25)', '5(0.5)', '1<(0.5)', '5>(0.75)', '7>(0.25)', '2(0.5)', '5(0.5)',
                  '1(0.75)', '3(0.25)', '5(0.5)', '1<(0.5)', '1(0.75)', '4(0.25)', '6(0.5)', '1<(0.5)', '1(0.75)', '3(0.25)', '5(0.5)', '1<(0.5)', '5>(0.75)', '7>(0.25)', '2(0.5)', '5(0.5)', '1(0.75)', '5(0.25)', '3(0.5)', '5(0.5)', '1(0.75)', '5(0.25)', '3(0.5)', '5(0.5)', '1(0.75)', '6(0.25)', '4(0.5)', '6(0.5)', '1(0.75)', '5(0.25)', '3(0.5)', '5(0.5)', '1(0.75)', '6(0.25)', '4(0.5)', '6(0.5)', '1(0.75)', '5(0.25)', '3(0.5)', '5(0.5)', '7>(0.75)', '5(0.25)', '2(0.5)', '5(0.5)', '1>(2)']
  }
複製代碼

額,是否是很複雜,很臃腫。。。它以簡譜爲載體,經過特殊符號來標記音高和時長,從而產生mainTrack和backingTrack兩個音軌,而後同步播放便可。這種實現雖然簡單,但有不少致命缺點:

  1. 不兼容通用的計算機曲譜格式,如musicxml
  2. 不能徹底表示音樂的全部維度,好比不少鋼琴譜不止有兩個音軌
  3. 過於抽象和複雜,不實用,很難製做這種識別格式
  4. 音樂專業人士: what are you 弄啥嘞?

因此筆者轉向另外一種實現思路,解析musicxml,但奈何這個過程耗時耗力,目前只完成了一半,部分細節尚未徹底解析正確,若是讀者有好的想法,能夠在評論區留言探討。

歡迎貢獻協做

FORK時,請遵循GPL開源協議。

最後

最後再貼一下體驗地址: www.autopiano.cn

歡迎體驗,分享。

解析musicxml的過程仍在進行中,若是某一天成功了,那麼示例演奏裏面就會加入海量的歌曲,以供學習,若是失敗了,額,那就是由於生活阻擋了我奮進的腳步。。。

1554105945684.jpg

原創不易,轉載分享時請註明出處~

相關文章
相關標籤/搜索