SkillAgentSearch skills...

Piano

🎹Play the piano with the keyboard - 用键盘8个键演奏一首蒲公英的约定送给自己或月亮代表我的心送给她

Install / Use

/learn @Wscats/Piano
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<div align="center"> <h1>Omi Piano</h1> <p> <strong>Build piano with Omi and Omi Snippets</strong> <br /><br /> <strong>基于Omi和Omi Snippets构建钢琴应用</strong> </p> <p> <sub>Made with ❤︎ by <a href="https://github.com/Wscats">Eno Yao</a> </sub> </p> <p> <a href="https://github.com/Wscats/piano"><a href="https://opencollective.com/piano" alt="Financial Contributors on Open Collective"><img src="https://opencollective.com/piano/all/badge.svg?label=financial+contributors" /></a> <img src="https://img.shields.io/badge/Star-500+-orange" /></a> <a href="https://wscats.github.io/piano/build/"><img src="https://img.shields.io/badge/Version-5.20-brightgreen" /></a> <a href="https://github.com/Wscats/piano"><img src="https://img.shields.io/badge/Github Page-Wscats-yellow" /></a> <a href="https://github.com/Wscats"><img src="https://img.shields.io/badge/Author-Eno Yao-blueviolet" /></a> </p> </div> <!-- <a href="https://github.com/Wscats/piano"><img src="https://img.shields.io/badge/Star-500+-orange" /></a> <a href="https://wscats.github.io/piano/build/"><img src="https://img.shields.io/badge/Version-5.20-brightgreen" /></a> <a href="https://github.com/Wscats/piano"><img src="https://img.shields.io/badge/Github Page-Wscats-yellow" /></a> <a href="https://github.com/Wscats"><img src="https://img.shields.io/badge/Author-Eno Yao-blueviolet" /></a> [![Netlify Status](https://api.netlify.com/api/v1/badges/b652768b-1673-42cd-98dd-3fd807b2ebca/deploy-status)](https://app.netlify.com/sites/determined-goldstine-52a037/deploys) -->

Usage

<img width="250px" align="right" src="./public/piano.gif"/>

体验地址: http://wscats.gitee.io/piano/build/ 或者 https://wscats.github.io/piano/build/

项目地址: https://github.com/Wscats/piano

<!-- <img height="80px" align="right" src="https://raw.githubusercontent.com/Wscats/piano/master/public/demo.png" /> -->

用键盘8个键演奏一首蒲公英的约定送给996的自己或者一首月亮代表我的心给七夕的她,非常简单~

这个项目仅仅用了几个简单的前端技术实现,献给每一位挚爱音乐的代码家🎹

如果你喜欢或者对你有帮助,给我点个赞支持下吧😊

Develop & Installation

<!-- <img src="./public/demo.png"> -->

开发,构建和运行。

# 获取远程仓库代码
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

技术点和目录结构

项目中没有使用市面主流的框架(React,Vue 和 Angular )和热门的技术,而用的是 Omi 框架(JSX+WebComponents),还有 Omil 的单文件组件 SFCs 加载器,组件通讯基于Proxy特性,并结合了 VScode 的插件 Eno-Snippets基于AST正则实时编译.eno或.omi 后缀组件减轻部分的 Webpack 的局部编译压力,当然其他同学们熟知的技术这里就不提及了。

<!-- <img width="560px" align="right" src="https://wscats.github.io/omi-docs/public/images/transfer.png" /> --> <img width="580px" align="right" src="./public/transfer.png" />
  • 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 后缀文件。

简单乐理知识

首先我们先补习点音乐基础,提前收集好最基本的钢琴单音素材,每个音符对应一份.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|

<img height="120px" align="right" src="./public/keys.png" />

这里专门制作一张图方便我们理解,请看右图:

当然实际情况还有全音和半音的区分,比如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 语法去遍历渲染黑白键。

<img src="https://raw.githubusercontent.com/Wscats/piano/master/public/demo.png" />
<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();
}
<img align="right" width="500" src="./public/keycode.png" />

完成 <audio> 的音频处理之后,就需要让键盘事件与其绑定逻辑了,这里需要了解键盘的 keycode,键盘每个实体按键都会对应有一个按键码,根据按键码用 JS 键盘事件监听来判断按键是否被摁住。

我们使用 window.document.onkeydown 来监听页面全局的键盘事件,然后判断事件对象 e.altKeye.ctrlKeye.metaKeye.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

Related Skills

View on GitHub
GitHub Stars1.2k
CategoryDevelopment
Updated2h ago
Forks158

Languages

JavaScript

Security Score

85/100

Audited on Mar 28, 2026

No findings