用鍵盤8個鍵演奏一首蒲公英的約定送給本身或者一首月亮表明個人心送給她

> 體驗地址: https://wscats.github.io/piano/build/css

> 項目地址: https://github.com/Wscats/pianohtml

用鍵盤8個鍵演奏一首蒲公英的約定送給996的本身或月亮表明個人心給七夕的她,很是簡單~前端

這個項目僅僅用了幾個簡單的前端技術實現,獻給每一位摯愛音樂的代碼家🎹git

若是你喜歡或者對你有幫助,給我點個贊支持下吧😊github

技術點和目錄結構

項目中沒有使用市面主流的框架(React,Vue 和 Angular )和熱門的技術,而用的是Omi框架(JSX+WebComponents),還有 Omil 的單文件組件 SFCs 加載器,組件通信基於Proxy特性,並結合了 VScode 的插件 Eno-Snippets基於AST正則實時編譯.eno或.omi 後綴組件減輕部分的 Webpack 的局部編譯壓力,固然其餘同窗們熟知的技術這裏就不說起了。web

  • src
    • assets
    • element
      • app-piano
        • songs 鋼琴簡譜目錄
        • app-piano.eno 單文件組件
        • app-piano.js 組件編譯後的JS文件
        • notes.js 鍵盤按鍵和音符的映射
    • index.js 組件根容器,配置Proxy的通訊方法
  • public
    • samples/piano 鋼琴單音符素材
app-piano.eno 開發中你須要編寫的單文件組件
app-piano.js 通過Eno-Snippets修改或者保存文件Hello.eno後通過插件轉化的js文件

以下圖,左邊的代碼是咱們編寫的 .eno 後綴的單文件組件,右邊是通過 Eno Snippets 生成的 .js 後綴文件。npm

Develop & Installation

開發,構建和運行。編程

# 獲取遠程倉庫代碼
git clone https://github.com/Wscats/piano
# 進入目錄
cd piano
# 安裝依賴
npm install
# 啓動項目
npm start
# 在瀏覽器訪問 http://localhost:3000

使用 npm 包管理器安裝。數組

npm install omi-piano

運行或者發佈屬於本身的演奏版本。瀏覽器

# 進入目錄
cd omi-piano
# 安裝依賴
npm install
# 啓動項目
npm start
# 發佈自已的演奏版本
npm run build

簡單樂理知識

首先咱們先補習點音樂基礎,提早收集好最基本的鋼琴單音素材,每一個音符對應一份.mp3文件,用一個對象記錄起來,相似下面這樣,舉個例子這裏的A指的是CDEFGAB音名中A也就是La,這是最基本的樂理,有沒有讓你想起小時候上音樂課,畫板上的五線譜。

export default {
  A2: "./samples/piano/a54.mp3",
  A3: "./samples/piano/a69.mp3",
  A4: "./samples/piano/a80.mp3",
  A5: "./samples/piano/a74.mp3",
  A6: "./samples/piano/a66.mp3",
  'A#3': "./samples/piano/b69.mp3",
  'A#4': "./samples/piano/b80.mp3",
  'A#5': "./samples/piano/b74.mp3",
  'A#6': "./samples/piano/b66.mp3",
  // other... 
}

固然這裏咱們使用數字來等價替代,下降初學者的難度,看下錶1等價於C中音也就是Do,因爲不少歌都會用到鋼琴更密集的中間部分按鍵,因此咱們默認中音對應數字鍵:

> 1 === C4 === Do

數字鍵 1 2 3 4 5 6 7
音名 C4 D4 E4 F4 G4 A4 B4
音符 Do Re Mi Fa Sol La Si

這裏專門製做一張圖方便咱們理解:

固然實際狀況還有全音和半音的區分,好比A的半音就是A#,還有中音,高音和倍高音,咱們這裏用A4表示中音,A5表示高音,A6表示倍高音,因此表格能夠繼續整理得更清晰,當咱們要彈奏中音A4,只須要按鍵盤上的數字鍵6,若是要彈奏高音A5,只須要用組合鍵Option+6,咱們只須要觸類旁通,就能夠知道每一個音符對應的鍵盤按鍵。

倍低音 C2 D2 E2 F2 G2 A2 B2
Shift鍵+(1-7) Shift+1 Shift+2 Shift+3 Shift+4 Shift+5 Shift+6 Shift+7
低音 C3 D3 E3 F3 G3 A3 B3
Ctrl鍵+(1-7) Ctrl+1 Ctrl+2 Ctrl+3 Ctrl+4 Ctrl+5 Ctrl+6 Ctrl+7
中音 C4 D4 E4 F4 G4 A4 B4
數字鍵1-7 1 2 3 4 5 6 7
高音 C5 D5 E5 F5 G5 A5 B5
Option鍵+(1-7) Option+1 Option+2 Option+3 Option+4 Option+5 Option+6 Option+7
倍高音 C6 D6 E6 F6 G6 A6 B6
Command鍵+(1-7) Command+1 Command+2 Command+3 Command+4 Command+5 Command+6 Command+7
音符 Do Re Mi Fa Sol La Si

上面是全音表,這裏附上半音表:

倍低半音 C#2 D#2 F#2 G#2 A#2
Shift+ Shift+q Shift+w Shift+e Shift+r Shift+t
低半音 C#3 D#3 F#3 G#3 A#3
Ctrl+ Ctrl+q Ctrl+w Ctrl+e Ctrl+r Ctrl+t
中半音 C#4 D#4 F#4 G#4 A#4
字母鍵 q w e r t
高半音 C#5 D#5 F#5 G#5 A#5
Option+ Option+q Option+w Option+e Option+r Option+t
倍高半音 C#6 D#6 F#6 G#6 A#6
Command+ Command+q Command+w Command+e Command+r Command+t

那麼咱們如今只須要用鍵盤上的5個字母鍵(q,w,e,r,t) + 4個功能鍵(Shift,Control,Option和Command) + 7個數字鍵(1,2,3,4,5,6,7)總共16個鍵,演奏鋼琴60個單音(35個全音+25個半音),實際狀況一首簡單的鋼琴曲能夠不須要用到那麼多,用幾個簡單的和絃便可。

構建鋼琴界面

有上面的前期準備,下面就是轉化爲咱們的編程知識了,咱們須要使用 HTML 來繪製咱們的鋼琴界面,咱們能夠參考 codepencodesandbox 的素材,這裏我用了 flex 佈局配合陰影和過分實現鋼琴的黑白鍵,裏面用了 React 的 JSX 語法去遍歷渲染黑白鍵。

<div class="piano">
  {this.data.pianoKeys.map((item)=&gt;{return(
  <div class="piano-key">
    <div data-type="white" ref="{e=">{ this[item.white.name] = e }} class="piano-key__white"
      onClick={this.playNote.bind(this,item.white.name)} data-key={item.white.keyCode}
      data-note={item.white.name}&gt;
      <span class="piano-note">{item.white.name}</span>
      <audio preload="auto" src="{this.data.notes[item.white.name]}" hidden="true" data-note="{item.white.name}" class="audioEle"></audio>
    </div>
    <div data-type="black" ref="{e=">{ this[item.black.name] = e }} style={{
      display: item.black.name ? 'block' : 'none'
    }} class="piano-key__black" onClick={this.playNote.bind(this,item.black.name)} data-key={item.black.keyCode}
      data-note={item.black.name}&gt;
      <span class="piano-note" style="color:#fff">{item.black.name}</span>
      <audio preload="auto" src="{this.data.notes[item.white.name]}" hidden="true" data-note="{item.white.name}" class="audioEle"></audio>
    </div>
  </div>
  )})}
</div>

能夠觀察 CSS 的源代碼,分別對應寫黑鍵和白鍵的樣式,還能夠另外寫多一個樣式,用於鍵盤或者鼠標點擊琴鍵時候的效果,能夠簡單給它加一個背景色便可,總體實現不會太複雜,具體能夠調整樣式的參數來打造屬於本身的鋼琴風格。

.piano {
  margin: 0 200px;
  background: linear-gradient(-65deg, #000, #222, #000, #666, #222 75%);
  border-top: .8rem solid #282828;
  box-shadow: inset 0 -1px 1px hsla(0, 0%, 100%, .5), inset -0.4rem 0.4rem #282828;
  display: flex;
  height: 80vh;
  height: 20vh;
  justify-content: center;
  overflow: hidden;
  padding-bottom: 2%;
  padding-left: 2.5%;
  padding-right: 2.5%;
}
.piano-key {
  color: blue;
  flex: 1;
  margin: 0 .1rem;
  max-width: 8.8rem;
  position: relative;
}

.piano-key__white {
  display: flex;
  flex-direction: column-reverse;
  background: linear-gradient(-30deg, #f8f8f8, #fff);
  box-shadow: inset 0 1px 0 #fff, inset 0 -1px 0 #fff, inset 1px 0 0 #fff, inset -1px 0 0 #fff, 0 4px 3px rgba(0, 0, 0, .7), inset 0 -1px 0 #fff, inset 1px 0 0 #fff, inset -1px -1px 15px rgba(0, 0, 0, .5), -3px 4px 6px rgba(0, 0, 0, .5);
  height: 100%;
  position: relative;
}

.piano-key__black {
  display: flex;
  flex-direction: column-reverse;
  background: linear-gradient(-20deg, #222, #000, #222);
  box-shadow: inset 0 -1px 2px hsla(0, 0%, 100%, .4), 0 2px 3px rgba(0, 0, 0, .4);
  border-width: .2rem .4rem 1.2rem;
  border-style: solid;
  border-color: #666 #222 #111 #555;
  height: 60%;
  left: 100%;
  position: absolute;
  transform: translateX(-50%);
  top: 0;
  width: 70%;
  z-index: 1;
}

播放鋼琴音

當咱們實現完鋼琴界面,咱們就須要爲每一個按鍵匹配聲音,這裏使用 HTML5 的 <audio> 標籤,它能夠裝載着鋼琴的音符,當咱們觸發鼠標點擊事件或者鍵盤點擊事件的時候,咱們就讓它播放,在鋼琴沒播放以前咱們使用屬性值 preload="auto" 讓其預加載。

<audio preload="auto" src="{this.data.notes[item.white.name]}" hidden="true" data-note="{item.white.name}" class="audioEle"></audio>

播放只要用ref屬性獲取琴音的節點,而後對其觸發方法控制播放邏輯,audio.currentTime = 0重置播放進度和audio.play()執行播放,當觸發播放的同時能夠用延時器實現按鍵動畫。

playNote(name) {
  let audio = this[name].childNodes[1]
  this[name].style.background = `linear-gradient(-20deg, #3330fb, #000, #222)`
  let timer = setTimeout(() =&gt; {
    this[name].getAttribute('data-type') === 'white' ? this[name].style.background = `linear-gradient(-30deg, #f8f8f8, #fff)` : this[name].style.background = `linear-gradient(-20deg, #222, #000, #222)`
    clearTimeout(timer)
  }, 1000)
  audio.currentTime = 0;
  audio.play();
}

完成 <audio> 的音頻處理以後,就須要讓鍵盤事件與其綁定邏輯了,這裏須要瞭解鍵盤的 keycode,鍵盤每一個實體按鍵都會對應有一個按鍵碼,根據按鍵碼用 JS 鍵盤事件監聽來判斷按鍵是否被摁住。

咱們使用 window.document.onkeydown 來監聽頁面全局的鍵盤事件,而後判斷事件對象 e.altKeye.ctrlKeye.metaKeye.shiftKey 這四個功能鍵是否被觸發,再判斷數字鍵是否被觸發,最後判斷字母鍵是否被觸發。

document.onkeydown = (event) =&gt; {
  var e = event || window.event || arguments.callee.caller.arguments[0];
  let playNote = (key) =&gt; {
    if (e.shiftKey === true) {
      this.playNote(`${key}2`)
    } else if (e.altKey === true) {
      this.playNote(`${key}5`)
    } else if (e.ctrlKey === true) {
      this.playNote(`${key}3`)
    } else if (e.metaKey === true) {
      this.playNote(`${key}6`)
      e.returnValue = false;
    } else {
      this.playNote(`${key}4`)
    }
  }
  if (e &amp;&amp; 49 &lt;= e.keyCode &amp;&amp; e.keyCode &lt;= 55) {
    switch (e.keyCode) {
      case 49:
        playNote('C')
        break;
      case 50:
        playNote('D')
        break;
      case 51:
        playNote('E')
        break;
      case 52:
        playNote('F')
        break;
      case 53:
        playNote('G')
        break;
      case 54:
        playNote('A')
        break;
      case 55:
        playNote('B')
        break;
    }
  }
  if (e &amp;&amp; (81 === e.keyCode || e.keyCode === 87 || e.keyCode === 69 || e.keyCode === 82 || e.keyCode === 84)) {
    switch (e.keyCode) {
      case 81:
        playNote('C#')
        break;
      case 87:
        playNote('D#')
        break;
      case 69:
        playNote('F#')
        break;
      case 82:
        playNote('G#')
        break;
      case 84:
        playNote('A#')
        break;
    }
  }
};

音符同步顯示

每自動按一個鋼琴鍵,能夠看到音符在下面跳動並自動高亮,這裏面涉及鋼琴組件和底部文字組件的通訊。咱們使用的是 Omi 自帶的 Store 功能來實現組件的通訊,本質上它是基於 Proxy 對數據進行劫持,當咱們改變一個數據的時候,能夠實時映射最新的狀態到另一個組件,從而完成組件的通訊,這裏我設置了一個 countsong 做爲兩個組件的通訊值,count 記錄的是點擊到了第幾個音符,而 song 是正在播放的鋼琴樂譜。

render(<my-app />, '#root', {
    data: {
        count: 0,
        song: []
    },
    sub() {
        this.data.count--
    },
    add() {
        this.data.count++
    },
    setSong(song) {
        // 構建新的數組,給它下標值來作索引
        let melody = [];
        song.map((item, index) =&gt; {
            melody.push({
                ...item,
                index
            })
        })
        // 處理成每30個音符一個數組,自動播放時候自動顯示按鍵
        for (let j = 0; j &lt; melody.length; j += 30) {
            this.data.song.push(melody.slice(j, j + 30))
        }
    }
})

自動播放

如下就是關於如何自動播放的邏輯,若是要演奏複雜的歌曲,特別是多和絃的狀況下,咱們能夠編寫好歌譜,而後交給編程自動演奏,下面是周杰倫《蒲公英的約定》的鋼琴簡譜,咱們用數組把每一個按鍵的音符記錄下來,而後只要用定時器或者遞歸把每一個音符取出來給函數識別,而後再觸發對應的 <audio> 標籤播放便可,這裏解釋下數組裏面的每一項,若是字符串裏面是數字的話就對應中音,也就是若是是'3',那就只須要按鍵盤的3,若是是'+3'那就是高音,那就是前面提到的用組合鍵 option + 3,若是是 +1..,那就是告訴編程,這裏要停頓兩個節拍,咱們本身實際演奏的時候就在這裏稍微停頓下控制旋律便可。

const song = [
    '3', '4',
    '5', '5', '5', '6', '7', '+1..',
    '+1', '+1', '7', '+2', '6', '5', '5', '5', '+2', '+1', '+1', '+3', '+3..',
    '+1', '+2', '+3', '+3', '+4', '+3', '+2', '+3', '+1', '+1', '6', '6',
    '6', '7', '+1', '+2', '+2', '+1', '7', '6', '+4', '+2',
    // 將願望...
    '+2..', '3', '4', '5',
    // 折飛機寄成信...
    '5', '5', '5', '6', '7', '+1..',
    '+1', '+1', '7', '+2', '6', '5', '5', '5', '+2', '+1', '+1', '+3', '+3..',
    '+1', '+2', '+3', '+3', '+4', '+3', '+2', '+3', '+1', '+1', '6', '6',
    '6', '7', '+1', '+2', '+2', '+1', '7', '6', '+4', '+2..',
    // 一塊兒長大的約定...
    '3', '5', '+1', '+3', '+3.', '+4', '+2..', '+2', '+5', '7', '+1..',
    '+3', '+4', '+5', '+1', '+1', '+2', '+3', '+3..',
    // 說好要一塊兒旅行...
    '3', '5', '+1.', '+3', '+3.', '+4', '+2..',
    // 是你現在...
    '+2', '+5', '7', '+1..',
    // 惟一堅持的任性
    '+3', '+4', '+5', '+1', '+1', '+2.', '+1', '+1',
    // 在走廊...
    '3', '4',
    '5', '5', '5', '6', '7', '+1..',
    '+1', '+1', '7', '+2', '6', '5', '5', '5', '+2', '+1', '+1', '+3', '+3..',
    '+1', '+2', '+3', '+3', '+4', '+3', '+2', '+3', '+1', '+1', '6', '6',
    '6', '7', '+1', '+2', '+2', '+1', '7', '6', '+4', '+2',
    // 一塊兒長大的約定...
    '3', '5', '+1', '+3', '+3.', '+4', '+2..', '+2', '+5', '7', '+1..',
    '+3', '+4', '+5', '+1', '+1', '+2', '+3', '+3..',
    // 說好要一塊兒旅行...
    '3', '5', '+1.', '+3', '+3.', '+4', '+2..',
    // 是你現在...
    '+2', '+5', '7', '+1..',
    // 惟一堅持的任性...
    '+3', '+4', '+5', '+1', '+1', '+2.', '+1', '+1',
    // 一塊兒長大的約定...
    '+6', '+5', '+3', '+2', '+1', '+3.', '+4', '+2..',
    '+6', '+5', '7', '+1..',
    // 與你聊不完的曾經...
    '+3', '+4', '+5', '+1', '+1', '+2', '+3', '+3..',
    // 而我已經分不清...
    '3', '5', '+1', '+3', '+3.', '+2', '+2', '+2..', '+2', '+5', '7', '+2', '+1', '+1',
    // 仍是錯過的愛情...
    '+3', '+4', '+5', '+1', '+1', '+2.', '+1', '+1..'
]
export default [...song]

有了上面的數組,咱們只須要編寫一個遞歸函數函數來遍歷數組,而後根據這種類數字的簡譜,把它轉爲音符 CDEFGAB,一開始的時候我用了定時器實現讀譜函數,後來發現,用定時器比較難控制,音符之間的停頓時間,相反用遞歸會比較容易實現,可是遞歸一樣很難實現暫停播放功能,由於從外部中斷遞歸函數也比較複雜,因此同窗們若是要本身實現鋼琴的話,在這個地方要稍微注意一下。下面代碼中出現的 Promise 配合 await, async 和定時器就是接受一個時間變量,來控制音符之間的停頓時間,而外層if(offset &lt; song.length &amp;&amp; this.store.data.song.length &gt; 0)判斷條件左邊的條件是判斷索引值要小於簡譜數組的長度,右邊就是外層傳入的判斷值做爲遞歸函數的終止邊界條件。

playSong(song) {
  this.setSong([...song])
  let offset = 0
  let time = 0
  let playSong = async () =&gt; {
    // 右邊是從外部來中斷遞歸
    if (offset &lt; song.length &amp;&amp; this.store.data.song.length &gt; 0) {
      switch (typeof song[offset]) {
        // 簡譜2演奏方法 根據 ++12345--6. 簡單旋律狀況
        case 'string':
          let letters = song[offset].match(/[0-9]/g)
          switch (letters.length) {
            case 1:
              time = this.handleString(song, offset)
              break
            default:
              time = this.handleStrings(song, offset)
              break
          }
          break
        // 簡譜1演奏方法 根據 CDEFGAB,複雜旋律狀況,好比有和絃
        case 'object':
          console.log(song[offset]['note'])
          time = song[offset]['time'];
          this.playNote(song[offset]['note'])
          break;
        case 'number':
          // 休止符
          switch (song[offset]) {
            case 0:
              time = 1000
              break
          }
          break
      }
      await new Promise((resolve) =&gt; {
        let timer = setTimeout(() =&gt; {
          clearInterval(timer)
          resolve()
        }, time)
      })
      offset++
      // 自定義事件,跟下面底部的音符自動跳動結合
      this.add()
      playSong()
    } else {
      // 暫停播放
      clearTimeout(this.timer)
      this.store.data.song = []
      this.store.data.count = 0
      return
    }
  }
  playSong()
}

蒲公英的約定

看完上面的數組簡譜固然確定會有同窗問,上文的數組裏面不止運用到8個鍵吧,若是仔細觀察,就會發現這裏只用了中音和高音,也就是純數字鍵(1-7)Option鍵的配合,連半音都沒用到,因此實際止用到了8個鍵而已,因此上面給編程識別的簡譜,轉化咱們人類識別的鍵盤譜,只須要稍微調整爲下面的按鍵組合便可。

'3', '4', '5', '5', '5', '6', '7', 'Option+1..',
'Option+1', 'Option+1', '7', 'Option+2', '6', '5', '5', '5', 'Option+2', 'Option+1', 'Option+1', 'Option+3', 'Option+3..',
'Option+1', 'Option+2', 'Option+3', 'Option+3', 'Option+4', 'Option+3', 'Option+2', 'Option+3', 'Option+1', 'Option+1', '6', '6',
'6', '7', 'Option+1', 'Option+2', 'Option+2', 'Option+1', '7', '6', 'Option+4', 'Option+2',
// 將願望...
'Option+2..', '3', '4', '5',
// 折飛機寄成信...
'5', '5', '5', '6', '7', 'Option+1..',
'Option+1', 'Option+1', '7', 'Option+2', '6', '5', '5', '5', 'Option+2', 'Option+1', 'Option+1', 'Option+3', 'Option+3..',
'Option+1', 'Option+2', 'Option+3', 'Option+3', 'Option+4', 'Option+3', 'Option+2', 'Option+3', 'Option+1', 'Option+1', '6', '6',
'6', '7', 'Option+1', 'Option+2', 'Option+2', 'Option+1', '7', '6', 'Option+4', 'Option+2..',
// 一塊兒長大的約定...
'3', '5', 'Option+1', 'Option+3', 'Option+3.', 'Option+4', 'Option+2..', 'Option+2', 'Option+5', '7', 'Option+1..',
'Option+3', 'Option+4', 'Option+5', 'Option+1', 'Option+1', 'Option+2', 'Option+3', 'Option+3..',
// 說好要一塊兒旅行...
'3', '5', 'Option+1.', 'Option+3', 'Option+3.', 'Option+4', 'Option+2..',
// 是你現在...
'Option+2', 'Option+5', '7', 'Option+1..',
// 惟一堅持的任性
'Option+3', 'Option+4', 'Option+5', 'Option+1', 'Option+1', 'Option+2.', 'Option+1', 'Option+1',
// 在走廊...
'3', '4', '5', '5', '5', '6', '7', 'Option+1..',
'Option+1', 'Option+1', '7', 'Option+2', '6', '5', '5', '5', 'Option+2', 'Option+1', 'Option+1', 'Option+3', 'Option+3..',
'Option+1', 'Option+2', 'Option+3', 'Option+3', 'Option+4', 'Option+3', 'Option+2', 'Option+3', 'Option+1', 'Option+1', '6', '6',
'6', '7', 'Option+1', 'Option+2', 'Option+2', 'Option+1', '7', '6', 'Option+4', 'Option+2',
// 一塊兒長大的約定...
'3', '5', 'Option+1', 'Option+3', 'Option+3.', 'Option+4', 'Option+2..', 'Option+2', 'Option+5', '7', 'Option+1..',
'Option+3', 'Option+4', 'Option+5', 'Option+1', 'Option+1', 'Option+2', 'Option+3', 'Option+3..',
// 說好要一塊兒旅行...
'3', '5', 'Option+1.', 'Option+3', 'Option+3.', 'Option+4', 'Option+2..',
// 是你現在...
'Option+2', 'Option+5', '7', 'Option+1..',
// 惟一堅持的任性...
'Option+3', 'Option+4', 'Option+5', 'Option+1', 'Option+1', 'Option+2.', 'Option+1', 'Option+1',
// 一塊兒長大的約定...
'Option+6', 'Option+5', 'Option+3', 'Option+2', 'Option+1', 'Option+3.', 'Option+4', 'Option+2..',
'Option+6', 'Option+5', '7', 'Option+1..',
// 與你聊不完的曾經...
'Option+3', 'Option+4', 'Option+5', 'Option+1', 'Option+1', 'Option+2', 'Option+3', 'Option+3..',
// 而我已經分不清...
'3', '5', 'Option+1', 'Option+3', 'Option+3.', 'Option+2', 'Option+2', 'Option+2..', 'Option+2', 'Option+5', '7', 'Option+2', 'Option+1', 'Option+1',
// 仍是錯過的愛情...
'Option+3', 'Option+4', 'Option+5', 'Option+1', 'Option+1', 'Option+2.', 'Option+1', 'Option+1..'

月亮表明個人心

咱們還能夠演奏另外一首耳熟能詳的的鋼琴曲《月亮表明個人心》。

'Ctrl+5', '1', '3', '5', '1', 'Ctrl+7', '3', '5', '5', '6', '7', 'Option+1', '6', '5', '3', '2', '1', '1', '1', '3', '2', '1', '1', '1', '2', '3', '2', '1', 'Ctrl+6', '2',

'3', '2', 'Ctrl+5', '1', '3', '5', '1', 'Ctrl+7', '3', '5', '5', '6', '7', 'Option+1', '6', '5', '3', '2', '1', '1', '1', '3', '2', '1', '1', '1', '2', '3', '2', '1',

'Ctrl+6', '2', '3', '2', '3', '5', '3', '2', '1', '5', 'Ctrl+7', 'Ctrl+6', 'Ctrl+7', 'Ctrl+6', 'Ctrl+7', 'Ctrl+6', 'Ctrl+5', '3', '5', '3', '2', '1', '5', 'Ctrl+7', 'Ctrl+6', 'Ctrl+7', '1', '1', '1', '2',

'3', '2', 'Ctrl+5', '1', '3', '5', '1', 'Ctrl+7', '3', '5', '5', '6', '7', 'Option+1', '6', '5', '3', '2', '1', '1', '1', '3', '2', '1', '1', '1', '2', '3', '2', 'Ctrl+6',

'Ctrl+7', '1', '2', '1', 'Ctrl+5', '1', '3', '5', '1', 'Ctrl+7', '3', '5', '5', '6', '7', 'Option+1', '6', '5', '3', '2', '1', '1', '1', '3', '2', '1', '1', '1', '2', '3',

'2', '1', 'Ctrl+6', '2', '3', '2', 'Ctrl+5', '1', '3', '5', '1', 'Ctrl+7', '3', '5', '5', '6', '7', 'Option+1', '6', '5', '3', '2', '1', '1', '1', '3', '2', '1', '1', '1',

'2', '3', '2', '1', 'Ctrl+6', '2', '3', '2', '3', '5', '3', '2', '1', '5', 'Ctrl+7', 'Ctrl+6', 'Ctrl+7', 'Ctrl+6', 'Ctrl+7', 'Ctrl+6', 'Ctrl+5', '3', '5', '3', '2', '1', '5', 'Ctrl+7', 'Ctrl+6', 'Ctrl+7',

'1', '1', '1', '2', '3', '2', 'Ctrl+5', '1', '3', '5', '1', 'Ctrl+7', '3', '5', '5', '6', '7', 'Option+1', '6', '5', '3', '2', '1', '1', '1', '3', '2', '1', '1', '1',

'2', '3', '2', 'Ctrl+6', 'Ctrl+7', '1', '2', '1'

Contributing

感謝音樂和編程的陪伴!也致敬各位奮鬥於996的代碼家,歡迎分享,也期待您貢獻代碼,提 PR ,在 issue 中討論問題,或者說說您的建議,正如 Leehom Wang 歌曲中唱到:

> 若是世界太危險,只有音樂最安全,帶着我進夢裏面,讓歌詞都實現💞 —— 《咱們的歌》

相關文章
相關標籤/搜索