体验地址:http://wscats.gitee.io/piano/…
我的项目地址:https://github.com/Wscats/piano 或 https://gitee.com/wscats/piano
用键盘 8 个键演奏一首蒲公英的约定送给 996 的本人或月亮代表我的心给七夕的她,非常简单~
这个我的项目仅仅用了几个简略的前端技术实现,献给每一位挚爱音乐的代码家🎹
如果你喜爱或者对你有帮忙,给我点个赞反对下吧😊
技术点和目录构造
我的项目中没有应用市面支流的框架(React,Vue 和 Angular)和热门的技术,而用的是 Omi 框架(JSX+WebComponents
),还有 Omil
的单文件组件 SFCs
加载器,组件通信基于 Proxy
个性,并联合了 VScode 的插件 Eno-Snippets
基于 AST
和正则
实时编译.eno 或.omi
后缀组件加重局部的 Webpack
的部分编译压力,当然其他同学们熟知的技术这里就不提及了。
-
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 后缀文件。
开发与装置
开发,构建和运行。
# 获取近程仓库代码
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 来绘制咱们的钢琴界面,咱们能够参考 codepen 和 codesandbox 的素材,这里我用了 flex
布局配合暗影和适度实现钢琴的黑白键,外面用了 React 的 JSX 语法去遍历渲染黑白键。
<div class="piano">
{this.data.pianoKeys.map((item)=>{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}>
<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}>
<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(() => {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.altKey
,e.ctrlKey
,e.metaKey
和 e.shiftKey
这四个功能键是否被触发,再判断数字键是否被触发,最初判断字母键是否被触发。
document.onkeydown = (event) => {var e = event || window.event || arguments.callee.caller.arguments[0];
let playNote = (key) => {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 && 49 <= e.keyCode && e.keyCode <= 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 && (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;
}
}
};
自动播放
以下就是对于如何自动播放的逻辑,如果要演奏简单的歌曲,特地是多和弦的状况下,咱们能够编写好歌谱,而后交给编程主动演奏,上面是 周杰伦《蒲公英的约定》
的钢琴简谱,咱们用数组把每个按键的音符记录下来,而后只有用定时器或者递归把每个音符取出来给函数辨认,而后再触发对应的 <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 < song.length && this.store.data.song.length > 0)
判断条件右边的条件是判断索引值要小于简谱数组的长度,左边就是外层传入的判断值作为递归函数的终止边界条件。
playSong(song) {this.setSong([...song])
let offset = 0
let time = 0
let playSong = async () => {
// 左边是从内部来中断递归
if (offset < song.length && this.store.data.song.length > 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) => {let timer = setTimeout(() => {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'
感激
感激音乐和编程的陪伴!也致敬各位奋斗于 996 的代码家,欢送分享,也期待您奉献代码,提 PR,在 issue 中探讨问题,或者说说您的倡议,正如 Leehom Wang 歌曲中唱到:
如果世界太危险,只有音乐最平安,带着我进梦外面,让歌词都实现💞 ——《咱们的歌》