├── public
├── demo.gif
├── demo.png
├── keys.png
├── logo.png
├── piano.gif
├── favicon.ico
├── keycode.png
├── transfer.png
├── samples
│ └── piano
│ │ ├── a48.mp3
│ │ ├── a49.mp3
│ │ ├── a50.mp3
│ │ ├── a51.mp3
│ │ ├── a52.mp3
│ │ ├── a53.mp3
│ │ ├── a54.mp3
│ │ ├── a55.mp3
│ │ ├── a56.mp3
│ │ ├── a57.mp3
│ │ ├── a65.mp3
│ │ ├── a66.mp3
│ │ ├── a67.mp3
│ │ ├── a68.mp3
│ │ ├── a69.mp3
│ │ ├── a70.mp3
│ │ ├── a71.mp3
│ │ ├── a72.mp3
│ │ ├── a73.mp3
│ │ ├── a74.mp3
│ │ ├── a75.mp3
│ │ ├── a76.mp3
│ │ ├── a77.mp3
│ │ ├── a78.mp3
│ │ ├── a79.mp3
│ │ ├── a80.mp3
│ │ ├── a81.mp3
│ │ ├── a82.mp3
│ │ ├── a83.mp3
│ │ ├── a84.mp3
│ │ ├── a85.mp3
│ │ ├── a86.mp3
│ │ ├── a87.mp3
│ │ ├── a88.mp3
│ │ ├── a89.mp3
│ │ ├── a90.mp3
│ │ ├── b49.mp3
│ │ ├── b50.mp3
│ │ ├── b52.mp3
│ │ ├── b53.mp3
│ │ ├── b54.mp3
│ │ ├── b56.mp3
│ │ ├── b57.mp3
│ │ ├── b66.mp3
│ │ ├── b67.mp3
│ │ ├── b68.mp3
│ │ ├── b69.mp3
│ │ ├── b71.mp3
│ │ ├── b72.mp3
│ │ ├── b73.mp3
│ │ ├── b74.mp3
│ │ ├── b76.mp3
│ │ ├── b79.mp3
│ │ ├── b80.mp3
│ │ ├── b81.mp3
│ │ ├── b83.mp3
│ │ ├── b84.mp3
│ │ ├── b86.mp3
│ │ ├── b87.mp3
│ │ ├── b89.mp3
│ │ └── b90.mp3
├── manifest.json
└── index.html
├── src
├── assets
│ └── index.css
├── elements
│ ├── app-piano
│ │ ├── songs
│ │ │ ├── ndda.js
│ │ │ ├── later.js
│ │ │ ├── fuji.js
│ │ │ ├── xxy.js
│ │ │ ├── test.js
│ │ │ ├── pgydyd.js
│ │ │ └── moon.js
│ │ ├── notes.js
│ │ ├── pianoKeys.js
│ │ ├── index.eno
│ │ └── index.js
│ ├── app
│ │ ├── index.js
│ │ ├── _index.less
│ │ └── logo.svg
│ ├── app-footer
│ │ ├── keys.js
│ │ ├── index.eno
│ │ └── index.js
│ └── app-header
│ │ ├── index.eno
│ │ └── index.js
└── index.js
├── .github
└── FUNDING.yml
├── config
├── jest
│ ├── fileTransform.js
│ └── cssTransform.js
├── entry.js
├── polyfills.js
├── paths.js
├── env.js
├── webpackDevServer.config.js
├── webpack.config.dev.js
└── webpack.config.prod.js
├── .gitignore
├── scripts
├── test.js
├── start.js
└── build.js
├── .eslintrc
├── package.json
├── introduce.md
└── README.md
/public/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/demo.gif
--------------------------------------------------------------------------------
/public/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/demo.png
--------------------------------------------------------------------------------
/public/keys.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/keys.png
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/logo.png
--------------------------------------------------------------------------------
/public/piano.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/piano.gif
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/favicon.ico
--------------------------------------------------------------------------------
/public/keycode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/keycode.png
--------------------------------------------------------------------------------
/public/transfer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/transfer.png
--------------------------------------------------------------------------------
/src/assets/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/public/samples/piano/a48.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a48.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a49.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a49.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a50.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a50.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a51.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a51.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a52.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a52.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a53.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a53.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a54.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a54.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a55.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a55.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a56.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a56.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a57.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a57.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a65.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a65.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a66.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a66.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a67.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a67.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a68.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a68.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a69.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a69.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a70.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a70.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a71.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a71.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a72.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a72.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a73.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a73.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a74.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a74.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a75.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a75.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a76.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a76.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a77.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a77.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a78.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a78.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a79.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a79.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a80.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a80.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a81.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a81.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a82.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a82.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a83.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a83.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a84.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a84.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a85.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a85.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a86.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a86.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a87.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a87.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a88.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a88.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a89.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a89.mp3
--------------------------------------------------------------------------------
/public/samples/piano/a90.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/a90.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b49.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b49.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b50.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b50.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b52.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b52.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b53.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b53.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b54.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b54.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b56.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b56.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b57.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b57.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b66.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b66.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b67.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b67.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b68.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b68.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b69.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b69.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b71.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b71.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b72.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b72.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b73.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b73.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b74.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b74.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b76.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b76.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b79.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b79.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b80.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b80.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b81.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b81.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b83.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b83.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b84.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b84.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b86.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b86.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b87.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b87.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b89.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b89.mp3
--------------------------------------------------------------------------------
/public/samples/piano/b90.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/piano/master/public/samples/piano/b90.mp3
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | open_collective: piano # Replace with a single Open Collective username
4 | custom: ['https://gitee.com/wscats/piano#license'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
5 |
--------------------------------------------------------------------------------
/src/elements/app-piano/songs/ndda.js:
--------------------------------------------------------------------------------
1 | // 你的答案
2 | const song = [
3 | // 也许世界就这样我也还在路上
4 | '1', '1', '1', '2', '1', '1', '1.', '1', '1', '1', '1', '2', '3', '3.',
5 | '5', '3', '3', '4', '3', '2.', '1', '1', '1', '1', '2', '1', '1', '1.',
6 | '-5', '1', '1', '1', '2', '3', '3.'
7 | ]
8 |
9 | export default [...song]
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/en/webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Omi App",
3 | "name": "Omi Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | # /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/src/elements/app/index.js:
--------------------------------------------------------------------------------
1 | import { define, WeElement } from 'omi'
2 | import '../app-header'
3 | import '../app-footer'
4 | import '../app-piano'
5 |
6 | define('my-app', class extends WeElement {
7 | render() {
8 | return (
9 |
14 | )
15 | }
16 | })
17 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/elements/app-piano/songs/later.js:
--------------------------------------------------------------------------------
1 | // -1 -2 -3 -4 -5 -6 -7
2 | // C3 D3 E3 F3 G3 A3 B3
3 |
4 | // 1 2 3 4 5 6 7
5 | // C4 D4 E4 F4 G4 A4 B4
6 |
7 | // +1 +2 +3 +4 +5 +6 +7
8 | // C5 D5 E5 F5 G5 A5 B5
9 |
10 | const song = [
11 | '3', '2', '1.', '1', '1.', '2', '3', '4', '3.', '4', '2', '1', '2', '2', '-7', '-7', '1', '1', '-6', '-6', '-7', '1', '1', '2'
12 | ]
13 |
14 | export default [...song]
15 |
--------------------------------------------------------------------------------
/src/elements/app/_index.less:
--------------------------------------------------------------------------------
1 | @bg-color: #222;
2 |
3 | .app {
4 | text-align: center;
5 | }
6 |
7 | .app-logo {
8 | animation: app-logo-spin infinite 20s linear;
9 | height: 80px;
10 | }
11 |
12 | .app-header {
13 | background-color: @bg-color;
14 | height: 150px;
15 | padding: 20px;
16 | color: white;
17 | }
18 |
19 | .app-title {
20 | font-size: 1.5em;
21 | }
22 |
23 | .app-logo {
24 | cursor: pointer;
25 | }
26 |
27 | @keyframes app-logo-spin {
28 | from {
29 | transform: rotate(0deg);
30 | }
31 | to {
32 | transform: rotate(360deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/config/entry.js:
--------------------------------------------------------------------------------
1 | let fs = require('fs'),
2 | fileList = [];
3 |
4 |
5 | if (typeof String.prototype.endsWith != 'function') {
6 | String.prototype.endsWith = function (suffix) {
7 | return this.indexOf(suffix, this.length - suffix.length) !== -1;
8 | };
9 | }
10 |
11 | function walk(path) {
12 | let dirList = fs.readdirSync(path);
13 | dirList.forEach(function (item) {
14 | if (!fs.statSync(path + '/' + item).isDirectory()) {
15 | if (item.endsWith('\.js')) {
16 | fileList.push(item.substr(0, item.length - 3));
17 | }
18 | } else {
19 | //walk(path + '/' + item);
20 | }
21 | });
22 | }
23 |
24 | walk('./src');
25 |
26 |
27 |
28 | module.exports = fileList;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { render } from 'omi'
2 | import './assets/index.css'
3 | import './elements/app'
4 |
5 |
6 | render(, '#root', {
7 | data: {
8 | count: 0,
9 | song: []
10 | },
11 | sub() {
12 | this.data.count--
13 | },
14 | add() {
15 | this.data.count++
16 | },
17 | setSong(song) {
18 | // 构建新的数组,给它下标值来做索引
19 | let melody = [];
20 | song.map((item, index) => {
21 | melody.push({
22 | ...item,
23 | index
24 | })
25 | })
26 | // 处理成每30个音符一个数组,自动播放时候自动显示按键
27 | for (let j = 0; j < melody.length; j += 30) {
28 | this.data.song.push(melody.slice(j, j + 30))
29 | }
30 | }
31 | })
32 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 | const jest = require('jest');
19 | let argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI or in coverage mode
22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) {
23 | argv.push('--watch');
24 | }
25 |
26 |
27 | jest.run(argv);
28 |
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19 | // We don't polyfill it in the browser--this is user's responsibility.
20 | if (process.env.NODE_ENV === 'test') {
21 | require('raf').polyfill(global);
22 | }
23 |
--------------------------------------------------------------------------------
/src/elements/app/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["prettier"],
4 | "plugins": ["prettier"],
5 | "env": {
6 | "browser": true,
7 | "mocha": true,
8 | "node": true,
9 | "es6": true
10 | },
11 | "parserOptions": {
12 | "ecmaFeatures": {
13 | "modules": true,
14 | "jsx": true
15 | }
16 | },
17 | "globals": {
18 | "sinon": true,
19 | "expect": true
20 | },
21 | "rules": {
22 | "prettier/prettier": "error",
23 | "no-cond-assign": 1,
24 | "no-empty": 0,
25 | "no-console": 1,
26 | "semi": [1, "never"],
27 | "camelcase": 0,
28 | "comma-style": 2,
29 | "comma-dangle": [2, "never"],
30 | "indent": ["error", 2],
31 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"],
32 | "no-trailing-spaces": [2, { "skipBlankLines": true }],
33 | "max-nested-callbacks": [2, 3],
34 | "no-eval": 2,
35 | "no-implied-eval": 2,
36 | "no-new-func": 2,
37 | "guard-for-in": 0,
38 | "eqeqeq": 0,
39 | "no-else-return": 2,
40 | "no-redeclare": 2,
41 | "no-dupe-keys": 2,
42 | "radix": 2,
43 | "strict": [2, "never"],
44 | "no-shadow": 0,
45 | "callback-return": [1, ["callback", "cb", "next", "done"]],
46 | "no-delete-var": 2,
47 | "no-undef-init": 2,
48 | "no-shadow-restricted-names": 2,
49 | "handle-callback-err": 0,
50 | "no-lonely-if": 2,
51 | "keyword-spacing": 2,
52 | "constructor-super": 2,
53 | "no-this-before-super": 2,
54 | "no-dupe-class-members": 2,
55 | "no-const-assign": 2,
56 | "prefer-spread": 2,
57 | "no-useless-concat": 2,
58 | "no-var": 2,
59 | "object-shorthand": 2,
60 | "prefer-arrow-callback": 2,
61 | "quotes": [1, "single"]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | Piano
23 |
24 |
25 |
28 |
29 |
39 |
40 |
--------------------------------------------------------------------------------
/src/elements/app-footer/keys.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // A
3 | A2: "Shift+6",
4 | A3: "Ctrl+6",
5 | A4: "6",
6 | A5: "Option+6",
7 | A6: "Command+6",
8 | 'A#3': "Ctrl+t",
9 | 'A#4': "t",
10 | 'A#5': "Option+t",
11 | 'A#6': "Command+t",
12 | // B
13 | B2: "Shift+7",
14 | B3: "Ctrl+7",
15 | B4: "7",
16 | B5: "Option+7",
17 | B6: "Command+7",
18 | // C
19 | C2: "Shift+1",
20 | C3: "Ctrl+1",
21 | C4: "1",
22 | C5: "Option+1",
23 | C6: "Command+1",
24 | C7: "",
25 | 'C#2': "Shift+q",
26 | 'C#3': "Ctrl+q",
27 | 'C#4': "q",
28 | 'C#5': "Option+q",
29 | 'C#6': "Command+q",
30 | // D
31 | D2: "Shift+2",
32 | D3: "Ctrl+2",
33 | D4: "2",
34 | D5: "Option+2",
35 | D6: "Command+2",
36 | 'D#2': "Shift+w",
37 | 'D#3': "Ctrl+w",
38 | 'D#4': "w",
39 | 'D#5': "Option+w",
40 | 'D#6': "Command+w",
41 | // E
42 | E2: "Shift+3",
43 | E3: "Ctrl+3",
44 | E4: "3",
45 | E5: "Option+3",
46 | E6: "Command+3",
47 | 'E#2': "Shift+e",
48 | 'E#3': "Ctrl+e",
49 | 'E#4': "e",
50 | 'E#5': "Option+e",
51 | 'E#6': "Command+e",
52 | // F
53 | F2: "Shift+4",
54 | F3: "Ctrl+4",
55 | F4: "4",
56 | F5: "Option+4",
57 | F6: "Command+4",
58 | 'E#2': "Shift+e",
59 | 'E#3': "Ctrl+e",
60 | 'E#4': "e",
61 | 'E#5': "Option+e",
62 | 'E#6': "Command+e",
63 | // G
64 | G2: "Shift+5",
65 | G3: "Ctrl+5",
66 | G4: "5",
67 | G5: "Option+5",
68 | G6: "Command+5",
69 | 'G#2': "./samples/piano/b53.mp3",
70 | 'G#3': "./samples/piano/b87.mp3",
71 | 'G#4': "./samples/piano/b79.mp3",
72 | 'G#5': "./samples/piano/b72.mp3",
73 | 'G#6': "./samples/piano/b86.mp3"
74 | }
--------------------------------------------------------------------------------
/src/elements/app-piano/songs/fuji.js:
--------------------------------------------------------------------------------
1 | // -1 -2 -3 -4 -5 -6 -7
2 | // C3 D3 E3 F3 G3 A3 B3
3 |
4 | // 1 2 3 4 5 6 7
5 | // C4 D4 E4 F4 G4 A4 B4
6 |
7 | // +1 +2 +3 +4 +5 +6 +7
8 | // C5 D5 E5 F5 G5 A5 B5
9 |
10 | const song = [
11 | '-5', '-6', '1', '2', '1', '2', '3',
12 | '3', '3', '2', '1', '2', '3',
13 | '3', '3', '2', '1', '2.', '1', '1', '-6', '1.', '2', '2', '3.',
14 | '-5', '-6', '1', '2', '1', '2', '1', '1', '5',
15 | '5', '3', '2', '3', '2', '1', '1', '3',
16 | // 今天想着你回家
17 | '3', '2', '1', '2.', '2', '2.', '1', '1', '-6', '2.',
18 | // 原谅我不再送花
19 | '-5', '-6', '1', '2', '1', '2', '3', '3', '5',
20 | // 伤口应要结疤
21 | '5', '3', '2', '1', '2', '3', '3', '6',
22 | // 花
23 | '6', '3', '2', '1', '2.', '1', '1', '6', '1.', '2', '2', '3', '5.',
24 | // 如若你非我不嫁
25 | '-5', '-6', '1', '2', '1', '2', '1', '1',
26 | // 彼此
27 | '6', '6', '5', '3', '3', '2', '1',
28 | // 一生一世
29 | '1', '3', '3', '3', '2', '1', '2.', '2', '2.', '1', '1', '-6', '1',
30 | // 谁都只得
31 | '1', '+1', '7', '+1', '6', '+1.', '6', '6', '5', '6', '5', '3', '2', '3', '5', '6', '5',
32 | // 要拥有
33 | '5', '5', '6', '5', '6.', '6', '6.', '5', '5', '6', '5', '3', '3',
34 | // 曾沿着
35 | '1', '2', '3', '5', '3', '2', '1', '1', '1', '2', '3', '6', '3', '2', '1',
36 | // 谁能凭爱意要富士山私有
37 | '1', '1', '+1.', '+1', '+1.', '6', '6', '+1', '6.', '5',
38 | // 何不把
39 | '5', '1', '+1', '7', '+1', '7', '6', '5', '6', '5', '3', '2', '3', '5', '6', '5',
40 | // 试管里找不到
41 | '5', '5', '6', '5', '6.', '6', '6.', '5', '5', '6', '5', '3', '3',
42 | // 前尘
43 | '1', '2', '3', '5', '3', '2', '1', '1', '1', '2', '3', '6', '3', '2', '1', '+1..',
44 | // 我绝不
45 | '+1', '6', '+1', '6', '5', '5', '5', '6', '5', '3', '2', '3', '2', '2', '1', '-6', '1', '1', '2',
46 | // 你好嫌不够
47 | '1', '1', '+1', '6', '+1', '+2', '+1', '+1', '+1', '+2', '+1', '6', '+1', '+3', '+2', '+2', '+1', '6', '+1', '+2', '1'
48 | ]
49 |
50 | export default [...song, ...song]
51 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right
62 |
--------------------------------------------------------------------------------
/config/env.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const paths = require('./paths');
6 |
7 | // Make sure that including paths.js after env.js will read .env variables.
8 | delete require.cache[require.resolve('./paths')];
9 |
10 | const NODE_ENV = process.env.NODE_ENV;
11 | if (!NODE_ENV) {
12 | throw new Error(
13 | 'The NODE_ENV environment variable is required but was not specified.'
14 | );
15 | }
16 |
17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
18 | var dotenvFiles = [
19 | `${paths.dotenv}.${NODE_ENV}.local`,
20 | `${paths.dotenv}.${NODE_ENV}`,
21 | // Don't include `.env.local` for `test` environment
22 | // since normally you expect tests to produce the same
23 | // results for everyone
24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`,
25 | paths.dotenv,
26 | ].filter(Boolean);
27 |
28 | // Load environment variables from .env* files. Suppress warnings using silent
29 | // if this file is missing. dotenv will never modify any environment variables
30 | // that have already been set. Variable expansion is supported in .env files.
31 | // https://github.com/motdotla/dotenv
32 | // https://github.com/motdotla/dotenv-expand
33 | dotenvFiles.forEach(dotenvFile => {
34 | if (fs.existsSync(dotenvFile)) {
35 | require('dotenv-expand')(
36 | require('dotenv').config({
37 | path: dotenvFile,
38 | })
39 | );
40 | }
41 | });
42 |
43 | // We support resolving modules according to `NODE_PATH`.
44 | // This lets you use absolute paths in imports inside large monorepos:
45 | // https://github.com/facebookincubator/create-react-app/issues/253.
46 | // It works similar to `NODE_PATH` in Node itself:
47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
50 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421
51 | // We also resolve them to make sure all tools using them work consistently.
52 | const appDirectory = fs.realpathSync(process.cwd());
53 | process.env.NODE_PATH = (process.env.NODE_PATH || '')
54 | .split(path.delimiter)
55 | .filter(folder => folder && !path.isAbsolute(folder))
56 | .map(folder => path.resolve(appDirectory, folder))
57 | .join(path.delimiter);
58 |
59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
60 | // injected into the application via DefinePlugin in Webpack configuration.
61 | const REACT_APP = /^REACT_APP_/i;
62 |
63 | function getClientEnvironment(publicUrl) {
64 | const raw = Object.keys(process.env)
65 | .filter(key => REACT_APP.test(key))
66 | .reduce(
67 | (env, key) => {
68 | env[key] = process.env[key];
69 | return env;
70 | },
71 | {
72 | // Useful for determining whether we’re running in production mode.
73 | // Most importantly, it switches React into the correct mode.
74 | NODE_ENV: process.env.NODE_ENV || 'development',
75 | // Useful for resolving the correct path to static assets in `public`.
76 | // For example,
.
77 | // This should only be used as an escape hatch. Normally you would put
78 | // images into the `src` and `import` them in code to get their paths.
79 | PUBLIC_URL: publicUrl,
80 | }
81 | );
82 | // Stringify all values so we can feed into Webpack DefinePlugin
83 | const stringified = {
84 | 'process.env': Object.keys(raw).reduce((env, key) => {
85 | env[key] = JSON.stringify(raw[key]);
86 | return env;
87 | }, {}),
88 | };
89 |
90 | return { raw, stringified };
91 | }
92 |
93 | module.exports = getClientEnvironment;
94 |
--------------------------------------------------------------------------------
/scripts/start.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'development';
5 | process.env.NODE_ENV = 'development';
6 |
7 | // Makes the script crash on unhandled rejections instead of silently
8 | // ignoring them. In the future, promise rejections that are not handled will
9 | // terminate the Node.js process with a non-zero exit code.
10 | process.on('unhandledRejection', err => {
11 | throw err;
12 | });
13 |
14 | // Ensure environment variables are read.
15 | require('../config/env');
16 |
17 | const fs = require('fs');
18 | const chalk = require('chalk');
19 | const webpack = require('webpack');
20 | const WebpackDevServer = require('webpack-dev-server');
21 | const clearConsole = require('react-dev-utils/clearConsole');
22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
23 | const {
24 | choosePort,
25 | createCompiler,
26 | prepareProxy,
27 | prepareUrls,
28 | } = require('react-dev-utils/WebpackDevServerUtils');
29 | const openBrowser = require('react-dev-utils/openBrowser');
30 | const paths = require('../config/paths');
31 | const config = require('../config/webpack.config.dev');
32 | const createDevServerConfig = require('../config/webpackDevServer.config');
33 |
34 | const useYarn = fs.existsSync(paths.yarnLockFile);
35 | const isInteractive = process.stdout.isTTY;
36 |
37 | // Warn and crash if required files are missing
38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
39 | process.exit(1);
40 | }
41 |
42 | // Tools like Cloud9 rely on this.
43 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
44 | const HOST = process.env.HOST || '0.0.0.0';
45 |
46 | if (process.env.HOST) {
47 | console.log(
48 | chalk.cyan(
49 | `Attempting to bind to HOST environment variable: ${chalk.yellow(
50 | chalk.bold(process.env.HOST)
51 | )}`
52 | )
53 | );
54 | console.log(
55 | `If this was unintentional, check that you haven't mistakenly set it in your shell.`
56 | );
57 | console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`);
58 | console.log();
59 | }
60 |
61 | // We attempt to use the default port but if it is busy, we offer the user to
62 | // run on a different port. `choosePort()` Promise resolves to the next free port.
63 | choosePort(HOST, DEFAULT_PORT)
64 | .then(port => {
65 | if (port == null) {
66 | // We have not found a port.
67 | return;
68 | }
69 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
70 | const appName = require(paths.appPackageJson).name;
71 | const urls = prepareUrls(protocol, HOST, port);
72 | // Create a webpack compiler that is configured with custom messages.
73 | const compiler = createCompiler(webpack, config, appName, urls, useYarn);
74 | // Load proxy config
75 | const proxySetting = require(paths.appPackageJson).proxy;
76 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
77 | // Serve webpack assets generated by the compiler over a web sever.
78 | const serverConfig = createDevServerConfig(
79 | proxyConfig,
80 | urls.lanUrlForConfig
81 | );
82 | const devServer = new WebpackDevServer(compiler, serverConfig);
83 | // Launch WebpackDevServer.
84 | devServer.listen(port, HOST, err => {
85 | if (err) {
86 | return console.log(err);
87 | }
88 | if (isInteractive) {
89 | clearConsole();
90 | }
91 | console.log(chalk.cyan('Starting the development server...\n'));
92 | openBrowser(urls.localUrlForBrowser);
93 | });
94 |
95 | ['SIGINT', 'SIGTERM'].forEach(function(sig) {
96 | process.on(sig, function() {
97 | devServer.close();
98 | process.exit();
99 | });
100 | });
101 | })
102 | .catch(err => {
103 | if (err && err.message) {
104 | console.log(err.message);
105 | }
106 | process.exit(1);
107 | });
108 |
--------------------------------------------------------------------------------
/src/elements/app-header/index.eno:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Omi Piano
7 |
An interactive piano keyboard for omi. Supports custom sounds,
8 | touch/click/keyboard events, and fully auto play song.
9 |
16 |
17 |
18 |
19 |
20 |
Try it by clicking, tapping, or using your keyboard 1 to 9:
21 |
鼠标点击钢琴键或者键盘按数字键 1 ~ 9:
22 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/elements/app-footer/index.js:
--------------------------------------------------------------------------------
1 | import { WeElement, define, h } from "omi";
2 | import moon from "../app-piano/songs/moon.js";
3 | import keys from "./keys.js";
4 |
5 | class AppFooter extends WeElement {
6 | constructor(...args) {
7 | super(...args);
8 |
9 | this.setSong = song => this.store.setSong(song);
10 | }
11 |
12 | render(props) {
13 | return h(
14 | "div",
15 | {
16 | class: "app-footer"
17 | },
18 | h("hr", {
19 | class: "mt-5"
20 | }),
21 | h(
22 | "div",
23 | {
24 | class: "row mt-5"
25 | },
26 | h(
27 | "div",
28 | {
29 | class: "col"
30 | },
31 | h(
32 | "div",
33 | {
34 | class: "text-center"
35 | },
36 | h(
37 | "p",
38 | {
39 | class: "mt-4"
40 | },
41 | "You can click on the keyboard and play the melody that belongs to you. Here is an example of a piano piece:"
42 | ),
43 | h(
44 | "p",
45 | null,
46 | "\u4F60\u53EF\u4EE5\u70B9\u51FB\u952E\u76D8\u4F9D\u987A\u5E8F\u6309\u4EE5\u4E0B\u952E\uFF0C\u63A7\u5236\u597D\u8282\u594F\u6F14\u594F\u5C5E\u4E8E\u4F60\u7684\u65CB\u5F8B\uFF0C\u4E0B\u9762\u662F\u4E00\u9996\u94A2\u7434\u66F2\u7684\u4F8B\u5B50:"
47 | ),
48 | h(
49 | "p",
50 | {
51 | class: "mt-4"
52 | },
53 | "Enjoy it!"
54 | ),
55 | this.store.data.song.map(item => {
56 | if (item[0].note) {
57 | return h(
58 | "p",
59 | {
60 | class: "mt-3 code"
61 | },
62 | h(
63 | "code",
64 | {
65 | class: "p-2 text-dark"
66 | },
67 | item.map(item2 => {
68 | if (item2.note) {
69 | return h(
70 | "span",
71 | {
72 | style: {
73 | color:
74 | this.store.data.count === item2.index
75 | ? "red"
76 | : "black"
77 | }
78 | },
79 | this.data.keys[item2.note],
80 | ","
81 | );
82 | }
83 | })
84 | )
85 | );
86 | }
87 | })
88 | )
89 | )
90 | ),
91 | h(
92 | "div",
93 | {
94 | class: "bg-yellow mt-5 py-5"
95 | },
96 | h(
97 | "div",
98 | {
99 | class: "container"
100 | },
101 | h(
102 | "div",
103 | {
104 | class: "text-center text-secondary"
105 | },
106 | "Made with ",
107 | h(
108 | "span",
109 | {
110 | role: "img",
111 | "aria-label": "keyboard emoji"
112 | },
113 | "\uD83C\uDFB5"
114 | ),
115 | "by ",
116 | h(
117 | "a",
118 | {
119 | class: "text-secondary",
120 | href: "https://github.com/Wscats"
121 | },
122 | h("strong", null, "@Eno Yao")
123 | )
124 | )
125 | )
126 | )
127 | );
128 | }
129 |
130 | install() {
131 | this.data = {
132 | title: "omi",
133 | song: [],
134 | keys
135 | };
136 | this.setSong(moon);
137 | }
138 | }
139 |
140 | AppFooter.css = `hr{margin:0 50px;border:0;border-top-color:currentcolor;border-top-style:none;border-top-width:0px;border-top:1px solid rgba(0,0,0,0.1)}hr{box-sizing:content-box;height:0;overflow:visible}.bg-yellow{background-color:#f8e8d5}.container{line-height:50px;height:50px;width:100%;margin-right:auto;margin-left:auto}.text-secondary{color:#6c757d !important}.text-center{text-align:center !important}.code{padding:0 250px}@media screen and (max-width: 1000px){.code{padding:0 20px}}code{overflow:hidden;background-color:#ececec;width:100%;display:block;padding:10px 0}`;
141 | AppFooter.use = [
142 | {
143 | count: "count",
144 | song: "song"
145 | }
146 | ];
147 | define("app-footer", AppFooter);
148 |
--------------------------------------------------------------------------------
/src/elements/app-header/index.js:
--------------------------------------------------------------------------------
1 | import { WeElement, define, h } from "omi";
2 |
3 | class AppHeader extends WeElement {
4 | render(props) {
5 | return h(
6 | "div",
7 | null,
8 | h(
9 | "div",
10 | {
11 | style: "background: rgb(51, 51, 51) none repeat scroll 0% 0%;"
12 | },
13 | h(
14 | "div",
15 | {
16 | class: "container"
17 | },
18 | h(
19 | "div",
20 | {
21 | class: "text-sm-center text-white py-5"
22 | },
23 | h("h1", null, "Omi Piano"),
24 | h(
25 | "p",
26 | null,
27 | "An interactive piano keyboard for omi. Supports custom sounds,",
28 | h("br", {
29 | class: "d-none d-sm-block"
30 | }),
31 | "touch/click/keyboard events, and fully auto play song."
32 | ),
33 | h(
34 | "div",
35 | {
36 | class: "mt-4"
37 | },
38 | h(
39 | "a",
40 | {
41 | class: "btn btn-outline-light btn-lg",
42 | href: "https://github.com/Wscats/piano"
43 | },
44 | "View docs on Github"
45 | ),
46 | h(
47 | "a",
48 | {
49 | class: "btn btn-outline-light btn-lg",
50 | href: "https://github.com/Wscats/piano"
51 | },
52 | "\u67E5\u770B\u8BE5\u9879\u76EEGithub\u5730\u5740"
53 | )
54 | )
55 | )
56 | )
57 | ),
58 | h(
59 | "div",
60 | {
61 | class: "text-center"
62 | },
63 | h(
64 | "p",
65 | {
66 | class: ""
67 | },
68 | "Try it by clicking, tapping, or using your keyboard 1 to 9:"
69 | ),
70 | h(
71 | "p",
72 | {
73 | class: ""
74 | },
75 | "\u9F20\u6807\u70B9\u51FB\u94A2\u7434\u952E\u6216\u8005\u952E\u76D8\u6309\u6570\u5B57\u952E 1 ~ 9:"
76 | ),
77 | h(
78 | "div",
79 | {
80 | style: "color: rgb(119, 119, 119);"
81 | },
82 | h(
83 | "svg",
84 | {
85 | fill: "currentColor",
86 | preserveAspectRatio: "xMidYMid meet",
87 | height: "32",
88 | width: "32",
89 | viewBox: "0 0 40 40",
90 | style: "vertical-align: middle;"
91 | },
92 | h(
93 | "g",
94 | null,
95 | h("path", {
96 | d:
97 | "m33.4 20l-13.4 13.4-13.4-13.4 2.5-2.3 9.3 9.3v-20.4h3.2v20.4l9.4-9.3z"
98 | })
99 | )
100 | )
101 | )
102 | )
103 | );
104 | }
105 | }
106 |
107 | AppHeader.css = `
108 | * {
109 | margin: 0;
110 | padding: 0;
111 | }
112 |
113 | .container {
114 | width: 100%;
115 | margin-right: auto;
116 | margin-left: auto;
117 |
118 | }
119 |
120 | .text-white {
121 | color: #fff !important;
122 |
123 | }
124 |
125 | .text-sm-center {
126 | text-align: center !important;
127 | }
128 |
129 | .mt-4,
130 | .my-4 {
131 | margin-top: 1.5rem !important;
132 | }
133 |
134 | .pb-5,
135 | .py-5 {
136 | padding-bottom: 3rem !important;
137 | }
138 |
139 | .pt-5,
140 | .py-5 {
141 | padding-top: 3rem !important;
142 | }
143 |
144 | .btn:not(:disabled):not(.disabled) {
145 |
146 | cursor: pointer;
147 |
148 | }
149 |
150 | .btn-group-lg>.btn,
151 | .btn-lg {
152 |
153 | padding: 5px 10px;
154 | font-size: 12px;
155 | line-height: 15px;
156 | border-radius: 3px;
157 |
158 | }
159 |
160 | .btn-outline-light {
161 |
162 | color: #f8f9fa;
163 | background-color: transparent;
164 | background-image: none;
165 | border-color: #f8f9fa;
166 |
167 | }
168 |
169 | .btn {
170 | display: inline-block;
171 | font-weight: 400;
172 | text-align: center;
173 | white-space: nowrap;
174 | vertical-align: middle;
175 | -webkit-user-select: none;
176 | -moz-user-select: none;
177 | -ms-user-select: none;
178 | user-select: none;
179 | border: 1px solid transparent;
180 | border-top-color: transparent;
181 | border-right-color: transparent;
182 | border-bottom-color: transparent;
183 | border-left-color: transparent;
184 | padding: 15px 7.5px;
185 | font-size: 15px;
186 | line-height: 15px;
187 | border: 1px solid #fff;
188 | border-radius: 5px;
189 | transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
190 | }
191 |
192 | a {
193 | margin: 0 10px;
194 | text-decoration: none;
195 | }
196 |
197 | .text-center {
198 | margin-top: 12px;
199 | text-align: center !important;
200 | }
201 |
202 | p {
203 | margin: 0 10px;
204 | }
205 | `;
206 | define("app-header", AppHeader);
207 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "omi-piano",
3 | "version": "0.2.0",
4 | "private": false,
5 | "keywords": [
6 | "piano",
7 | "music",
8 | "mp3",
9 | "omi",
10 | "omijs",
11 | "omi.js",
12 | "eno",
13 | "eno yao",
14 | "yao",
15 | "wscats",
16 | "yjl",
17 | "css",
18 | "scss",
19 | "sass",
20 | "lemon",
21 | "laoyao",
22 | "one",
23 | "jing",
24 | "jialong",
25 | "yaojialong",
26 | "xie",
27 | "DK",
28 | "lan",
29 | "wang",
30 | "sha",
31 | "react",
32 | "jsx",
33 | "sfc",
34 | "component",
35 | "vue",
36 | "single file component",
37 | "老姚",
38 | "姚",
39 | "一一",
40 | "htm",
41 | "html",
42 | "reky"
43 | ],
44 | "dependencies": {
45 | "@babel/cli": "^7.0.0",
46 | "@babel/core": "^7.2.2",
47 | "@babel/plugin-proposal-class-properties": "^7.2.3",
48 | "@babel/plugin-proposal-decorators": "^7.2.3",
49 | "@babel/plugin-proposal-function-bind": "^7.2.0",
50 | "@babel/plugin-proposal-object-rest-spread": "^7.2.0",
51 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
52 | "@babel/plugin-transform-runtime": "^7.2.0",
53 | "@babel/preset-env": "^7.2.3",
54 | "@babel/preset-react": "^7.0.0",
55 | "@babel/runtime": "^7.3.1",
56 | "autoprefixer": "7.1.6",
57 | "babel-core": "^7.0.0-bridge.0",
58 | "babel-eslint": "7.2.3",
59 | "babel-jest": "20.0.3",
60 | "babel-loader": "^8.0.5",
61 | "case-sensitive-paths-webpack-plugin": "2.1.1",
62 | "chalk": "1.1.3",
63 | "css": "^2.2.4",
64 | "css-loader": "^1.0.1",
65 | "dotenv": "4.0.0",
66 | "dotenv-expand": "4.2.0",
67 | "eslint": "^4.18.2",
68 | "eslint-config-prettier": "^3.1.0",
69 | "eslint-loader": "1.9.0",
70 | "eslint-plugin-flowtype": "2.39.1",
71 | "eslint-plugin-import": "2.8.0",
72 | "eslint-plugin-jsx-a11y": "5.1.1",
73 | "eslint-plugin-prettier": "^3.0.0",
74 | "extract-text-webpack-plugin": "^4.0.0-beta.0",
75 | "file": "^0.2.2",
76 | "file-loader": "^2.0.0",
77 | "fs-extra": "3.0.1",
78 | "html-webpack-plugin": "^4.0.0-beta.5",
79 | "jest": "20.0.4",
80 | "less": "^3.9.0",
81 | "less-loader": "^4.1.0",
82 | "mini-css-extract-plugin": "^0.5.0",
83 | "object-assign": "4.1.1",
84 | "omi": "latest",
85 | "omi-router": "latest",
86 | "omil": "latest",
87 | "omio": "latest",
88 | "omiu": "latest",
89 | "opencollective-postinstall": "^2.0.2",
90 | "optimize-css-assets-webpack-plugin": "^5.0.1",
91 | "postcss-flexbugs-fixes": "3.2.0",
92 | "postcss-loader": "2.0.8",
93 | "prettier": "^1.14.3",
94 | "promise": "8.0.1",
95 | "raf": "3.4.0",
96 | "react-dev-utils": "^7.0.1",
97 | "reomi": "latest",
98 | "resolve": "1.6.0",
99 | "style-loader": "0.19.0",
100 | "sw-precache-webpack-plugin": "^0.11.5",
101 | "to-string-loader": "^1.1.5",
102 | "url": "^0.11.0",
103 | "url-loader": "^1.1.2",
104 | "webpack": "^4.28.3",
105 | "webpack-cli": "^3.2.0",
106 | "webpack-dev-server": "^3.1.10",
107 | "webpack-manifest-plugin": "^2.0.4",
108 | "webpack-merge": "^4.2.1",
109 | "whatwg-fetch": "2.0.3"
110 | },
111 | "scripts": {
112 | "start": "node scripts/start.js",
113 | "build": "PUBLIC_URL=. node scripts/build.js",
114 | "build-windows": "set PUBLIC_URL=.&& node scripts/build.js",
115 | "fix": "eslint src --fix",
116 | "postinstall": "opencollective-postinstall || true"
117 | },
118 | "jest": {
119 | "collectCoverageFrom": [
120 | "src/**/*.{js,jsx,mjs}"
121 | ],
122 | "setupFiles": [
123 | "/config/polyfills.js"
124 | ],
125 | "testMatch": [
126 | "/src/**/__tests__/**/*.{js,jsx,mjs}",
127 | "/src/**/?(*.)(spec|test).{js,jsx,mjs}"
128 | ],
129 | "testEnvironment": "node",
130 | "testURL": "http://localhost",
131 | "transform": {
132 | "^.+\\.(js|jsx|mjs)$": "/node_modules/babel-jest",
133 | "^.+\\.css$": "/config/jest/cssTransform.js",
134 | "^(?!.*\\.(js|jsx|mjs|css|json)$)": "/config/jest/fileTransform.js"
135 | },
136 | "transformIgnorePatterns": [
137 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$"
138 | ],
139 | "moduleNameMapper": {
140 | "^react-native$": "react-native-web"
141 | },
142 | "moduleFileExtensions": [
143 | "web.js",
144 | "js",
145 | "json",
146 | "web.jsx",
147 | "jsx",
148 | "node",
149 | "mjs"
150 | ]
151 | },
152 | "babel": {
153 | "presets": [
154 | "@babel/preset-env",
155 | [
156 | "@babel/preset-react",
157 | {
158 | "pragma": "Omi.h"
159 | }
160 | ]
161 | ],
162 | "plugins": [
163 | "@babel/plugin-proposal-class-properties",
164 | "@babel/transform-runtime",
165 | [
166 | "@babel/plugin-proposal-decorators",
167 | {
168 | "legacy": true
169 | }
170 | ],
171 | "@babel/plugin-proposal-function-bind",
172 | "@babel/plugin-proposal-object-rest-spread",
173 | "@babel/plugin-syntax-dynamic-import"
174 | ]
175 | },
176 | "prettier": {
177 | "singleQuote": true,
178 | "semi": false,
179 | "tabWidth": 2,
180 | "useTabs": false
181 | },
182 | "alias": {
183 | "omi": "omio"
184 | },
185 | "collective": {
186 | "type": "opencollective",
187 | "url": "https://opencollective.com/piano"
188 | }
189 | }
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'production';
5 | process.env.NODE_ENV = 'production';
6 |
7 | // Makes the script crash on unhandled rejections instead of silently
8 | // ignoring them. In the future, promise rejections that are not handled will
9 | // terminate the Node.js process with a non-zero exit code.
10 | process.on('unhandledRejection', err => {
11 | throw err;
12 | });
13 |
14 | // Ensure environment variables are read.
15 | require('../config/env');
16 |
17 | const path = require('path');
18 | const chalk = require('chalk');
19 | const fs = require('fs-extra');
20 | const webpack = require('webpack');
21 | const config = require('../config/webpack.config.prod');
22 | const paths = require('../config/paths');
23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
24 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
25 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
26 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
27 | const printBuildError = require('react-dev-utils/printBuildError');
28 |
29 | const measureFileSizesBeforeBuild =
30 | FileSizeReporter.measureFileSizesBeforeBuild;
31 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
32 | const useYarn = fs.existsSync(paths.yarnLockFile);
33 |
34 | // These sizes are pretty large. We'll warn for bundles exceeding them.
35 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
36 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
37 |
38 | // Warn and crash if required files are missing
39 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
40 | process.exit(1);
41 | }
42 |
43 | // First, read the current file sizes in build directory.
44 | // This lets us display how much they changed later.
45 | measureFileSizesBeforeBuild(paths.appBuild)
46 | .then(previousFileSizes => {
47 | // Remove all content but keep the directory so that
48 | // if you're in it, you don't end up in Trash
49 | fs.emptyDirSync(paths.appBuild);
50 | // Merge with the public folder
51 | copyPublicFolder();
52 | // Start the webpack build
53 | return build(previousFileSizes);
54 | })
55 | .then(
56 | ({ stats, previousFileSizes, warnings }) => {
57 | if (warnings.length) {
58 | console.log(chalk.yellow('Compiled with warnings.\n'));
59 | console.log(warnings.join('\n\n'));
60 | console.log(
61 | '\nSearch for the ' +
62 | chalk.underline(chalk.yellow('keywords')) +
63 | ' to learn more about each warning.'
64 | );
65 | console.log(
66 | 'To ignore, add ' +
67 | chalk.cyan('// eslint-disable-next-line') +
68 | ' to the line before.\n'
69 | );
70 | } else {
71 | console.log(chalk.green('Compiled successfully.\n'));
72 | }
73 |
74 | console.log('File sizes after gzip:\n');
75 | printFileSizesAfterBuild(
76 | stats,
77 | previousFileSizes,
78 | paths.appBuild,
79 | WARN_AFTER_BUNDLE_GZIP_SIZE,
80 | WARN_AFTER_CHUNK_GZIP_SIZE
81 | );
82 | console.log();
83 |
84 | const appPackage = require(paths.appPackageJson);
85 | const publicUrl = paths.publicUrl;
86 | const publicPath = config.output.publicPath;
87 | const buildFolder = path.relative(process.cwd(), paths.appBuild);
88 | printHostingInstructions(
89 | appPackage,
90 | publicUrl,
91 | publicPath,
92 | buildFolder,
93 | useYarn
94 | );
95 | },
96 | err => {
97 | console.log(chalk.red('Failed to compile.\n'));
98 | printBuildError(err);
99 | process.exit(1);
100 | }
101 | );
102 |
103 | // Create the production build and print the deployment instructions.
104 | function build(previousFileSizes) {
105 | console.log('Creating an optimized production build...');
106 |
107 | let compiler = webpack(config);
108 | return new Promise((resolve, reject) => {
109 | compiler.run((err, stats) => {
110 | if (err) {
111 | return reject(err);
112 | }
113 | const messages = formatWebpackMessages(stats.toJson({}, true));
114 | if (messages.errors.length) {
115 | // Only keep the first error. Others are often indicative
116 | // of the same problem, but confuse the reader with noise.
117 | if (messages.errors.length > 1) {
118 | messages.errors.length = 1;
119 | }
120 | return reject(new Error(messages.errors.join('\n\n')));
121 | }
122 | if (
123 | process.env.CI &&
124 | (typeof process.env.CI !== 'string' ||
125 | process.env.CI.toLowerCase() !== 'false') &&
126 | messages.warnings.length
127 | ) {
128 | console.log(
129 | chalk.yellow(
130 | '\nTreating warnings as errors because process.env.CI = true.\n' +
131 | 'Most CI servers set it automatically.\n'
132 | )
133 | );
134 | return reject(new Error(messages.warnings.join('\n\n')));
135 | }
136 | return resolve({
137 | stats,
138 | previousFileSizes,
139 | warnings: messages.warnings,
140 | });
141 | });
142 | });
143 | }
144 |
145 | function copyPublicFolder() {
146 | fs.copySync(paths.appPublic, paths.appBuild, {
147 | dereference: true,
148 | filter: file => file !== paths.appHtml,
149 | });
150 | }
151 |
--------------------------------------------------------------------------------
/src/elements/app-piano/pianoKeys.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export default[{
4 | black: { name: "C#2", keyCode: 81 },
5 | white: { name: "C2", keyCode: 49 },
6 | },
7 | {
8 | black: { name: "D#2", keyCode: 87 },
9 | white: { name: "D2", keyCode: 50 },
10 | },
11 | {
12 | black: { name: null, keyCode: null },
13 | white: { name: "E2", keyCode: 51 },
14 | },
15 | {
16 | black: { name: "F#2", keyCode: 69 },
17 | white: { name: "F2", keyCode: 52 },
18 | },
19 | {
20 | black: { name: "G#2", keyCode: 82 },
21 | white: { name: "G2", keyCode: 53 },
22 | },
23 | {
24 | black: { name: "A#2", keyCode: 84 },
25 | white: { name: "A2", keyCode: 54 },
26 | },
27 | {
28 | black: { name: null, keyCode: null },
29 | white: { name: "B2", keyCode: 55 },
30 | },
31 | {
32 | black: { name: "C#3", keyCode: 81 },
33 | white: { name: "C3", keyCode: 49 },
34 | },
35 | {
36 | black: { name: "D#3", keyCode: 87 },
37 | white: { name: "D3", keyCode: 50 },
38 | },
39 | {
40 | black: { name: null, keyCode: null },
41 | white: { name: "E3", keyCode: 51 },
42 | },
43 | {
44 | black: { name: "F#3", keyCode: 69 },
45 | white: { name: "F3", keyCode: 52 },
46 | },
47 | {
48 | black: { name: "G#3", keyCode: 82 },
49 | white: { name: "G3", keyCode: 53 },
50 | },
51 | {
52 | black: { name: "A#3", keyCode: 84 },
53 | white: { name: "A3", keyCode: 54 },
54 | },
55 | {
56 | black: { name: null, keyCode: null },
57 | white: { name: "B3", keyCode: 55 },
58 | },
59 | {
60 | black: { name: "C#4", keyCode: 81 },
61 | white: { name: "C4", keyCode: 49 },
62 | },
63 | {
64 | black: { name: "D#4", keyCode: 87 },
65 | white: { name: "D4", keyCode: 50 },
66 | },
67 | {
68 | black: { name: null, keyCode: null },
69 | white: { name: "E4", keyCode: 51 },
70 | },
71 | {
72 | black: { name: "F#4", keyCode: 69 },
73 | white: { name: "F4", keyCode: 52 },
74 | },
75 | {
76 | black: { name: "G#4", keyCode: 82 },
77 | white: { name: "G4", keyCode: 53 },
78 | },
79 | {
80 | black: { name: "A#4", keyCode: 84 },
81 | white: { name: "A4", keyCode: 54 },
82 | },
83 | {
84 | black: { name: null, keyCode: null },
85 | white: { name: "B4", keyCode: 55 },
86 | },
87 | {
88 | black: { name: "C#5", keyCode: 81 },
89 | white: { name: "C5", keyCode: 49 },
90 | },
91 | {
92 | black: { name: "D#5", keyCode: 87 },
93 | white: { name: "D5", keyCode: 50 },
94 | },
95 | {
96 | black: { name: null, keyCode: null },
97 | white: { name: "E5", keyCode: 51 },
98 | },
99 | {
100 | black: { name: "F#5", keyCode: 69 },
101 | white: { name: "F5", keyCode: 52 },
102 | },
103 | {
104 | black: { name: "G#5", keyCode: 82 },
105 | white: { name: "G5", keyCode: 53 },
106 | },
107 | {
108 | black: { name: "A#5", keyCode: 84 },
109 | white: { name: "A5", keyCode: 54 },
110 | },
111 | {
112 | black: { name: null, keyCode: null },
113 | white: { name: "B5", keyCode: 55 },
114 | },
115 | {
116 | black: { name: "C#6", keyCode: 81 },
117 | white: { name: "C6", keyCode: 49 },
118 | },
119 | {
120 | black: { name: "D#6", keyCode: 87 },
121 | white: { name: "D6", keyCode: 50 },
122 | },
123 | {
124 | black: { name: null, keyCode: null },
125 | white: { name: "E6", keyCode: 51 },
126 | },
127 | {
128 | black: { name: "F#6", keyCode: 69 },
129 | white: { name: "F6", keyCode: 52 },
130 | },
131 | {
132 | black: { name: "G#6", keyCode: 82 },
133 | white: { name: "G6", keyCode: 53 },
134 | },
135 | {
136 | black: { name: "A#6", keyCode: 84 },
137 | white: { name: "A6", keyCode: 54 },
138 | },
139 | {
140 | black: { name: null, keyCode: null },
141 | white: { name: "B6", keyCode: 55 }
142 | }]
143 |
144 | // 创建音符和键盘的映射关系表 用于生成上面的数组
145 | const createPianoKeys = () => {
146 | // 存放钢琴的按键顺序
147 | let pianoKeys = [];
148 | [2, 3, 4, 5, 6].map((item) => {
149 | pianoKeys = pianoKeys.concat([{
150 | white: {
151 | name: `C${item}`,
152 | keyCode: 49
153 | },
154 | black: {
155 | name: `C#${item}`,
156 | keyCode: 81
157 | }
158 | }, {
159 | white: {
160 | name: `D${item}`,
161 | keyCode: 50
162 | },
163 | black: {
164 | name: `D#${item}`,
165 | keyCode: 87
166 | }
167 | }, {
168 | white: {
169 | name: `E${item}`,
170 | keyCode: 51
171 | },
172 | black: {
173 | name: null,
174 | keyCode: null
175 | }
176 | }, {
177 | white: {
178 | name: `F${item}`,
179 | keyCode: 52
180 | },
181 | black: {
182 | name: `F#${item}`,
183 | keyCode: 69
184 | }
185 | }, {
186 | white: {
187 | name: `G${item}`,
188 | keyCode: 53
189 | },
190 | black: {
191 | name: `G#${item}`,
192 | keyCode: 82
193 | }
194 | }, {
195 | white: {
196 | name: `A${item}`,
197 | keyCode: 54
198 | },
199 | black: {
200 | name: `A#${item}`,
201 | keyCode: 84
202 | }
203 | }, {
204 | white: {
205 | name: `B${item}`,
206 | keyCode: 55
207 | },
208 | black: {
209 | name: null,
210 | keyCode: null
211 | }
212 | }])
213 | })
214 | return pianoKeys
215 | }
--------------------------------------------------------------------------------
/config/webpackDevServer.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
4 | const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
5 | const ignoredFiles = require('react-dev-utils/ignoredFiles');
6 | const config = require('./webpack.config.dev');
7 | const paths = require('./paths');
8 |
9 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
10 | const host = process.env.HOST || '0.0.0.0';
11 |
12 | // const fileList = require('./entry');
13 |
14 | // let rewrites = [];
15 |
16 | // fileList.forEach(function (item) {
17 |
18 | // rewrites.push({ from: new RegExp('^\/' + item + '.html', 'g'), to: '/build/' + item + '.html' })
19 |
20 | // });
21 |
22 |
23 |
24 | module.exports = function(proxy, allowedHost) {
25 | return {
26 | // WebpackDevServer 2.4.3 introduced a security fix that prevents remote
27 | // websites from potentially accessing local content through DNS rebinding:
28 | // https://github.com/webpack/webpack-dev-server/issues/887
29 | // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
30 | // However, it made several existing use cases such as development in cloud
31 | // environment or subdomains in development significantly more complicated:
32 | // https://github.com/facebookincubator/create-react-app/issues/2271
33 | // https://github.com/facebookincubator/create-react-app/issues/2233
34 | // While we're investigating better solutions, for now we will take a
35 | // compromise. Since our WDS configuration only serves files in the `public`
36 | // folder we won't consider accessing them a vulnerability. However, if you
37 | // use the `proxy` feature, it gets more dangerous because it can expose
38 | // remote code execution vulnerabilities in backends like Django and Rails.
39 | // So we will disable the host check normally, but enable it if you have
40 | // specified the `proxy` setting. Finally, we let you override it if you
41 | // really know what you're doing with a special environment variable.
42 | disableHostCheck:
43 | !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
44 | // Enable gzip compression of generated files.
45 | compress: true,
46 | // Silence WebpackDevServer's own logs since they're generally not useful.
47 | // It will still show compile warnings and errors with this setting.
48 | clientLogLevel: 'none',
49 | // By default WebpackDevServer serves physical files from current directory
50 | // in addition to all the virtual build products that it serves from memory.
51 | // This is confusing because those files won’t automatically be available in
52 | // production build folder unless we copy them. However, copying the whole
53 | // project directory is dangerous because we may expose sensitive files.
54 | // Instead, we establish a convention that only files in `public` directory
55 | // get served. Our build script will copy `public` into the `build` folder.
56 | // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
57 | //
58 | // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
59 | // Note that we only recommend to use `public` folder as an escape hatch
60 | // for files like `favicon.ico`, `manifest.json`, and libraries that are
61 | // for some reason broken when imported through Webpack. If you just want to
62 | // use an image, put it in `src` and `import` it from JavaScript instead.
63 | contentBase: paths.appPublic,
64 | // By default files from `contentBase` will not trigger a page reload.
65 | watchContentBase: true,
66 | // Enable hot reloading server. It will provide /sockjs-node/ endpoint
67 | // for the WebpackDevServer client so it can learn when the files were
68 | // updated. The WebpackDevServer client is included as an entry point
69 | // in the Webpack development configuration. Note that only changes
70 | // to CSS are currently hot reloaded. JS changes will refresh the browser.
71 | hot: true,
72 | // It is important to tell WebpackDevServer to use the same "root" path
73 | // as we specified in the config. In development, we always serve from /.
74 | publicPath: config.output.publicPath,
75 | // WebpackDevServer is noisy by default so we emit custom message instead
76 | // by listening to the compiler events with `compiler.plugin` calls above.
77 | quiet: true,
78 | // Reportedly, this avoids CPU overload on some systems.
79 | // https://github.com/facebookincubator/create-react-app/issues/293
80 | // src/node_modules is not ignored to support absolute imports
81 | // https://github.com/facebookincubator/create-react-app/issues/1065
82 | watchOptions: {
83 | ignored: ignoredFiles(paths.appSrc),
84 | },
85 | // Enable HTTPS if the HTTPS environment variable is set to 'true'
86 | https: protocol === 'https',
87 | host: host,
88 | overlay: false,
89 | historyApiFallback: {
90 | // Paths with dots should still use the history fallback.
91 | // See https://github.com/facebookincubator/create-react-app/issues/387.
92 | disableDotRule: true
93 | //rewrites: rewrites
94 | },
95 | public: allowedHost,
96 | proxy,
97 | before(app) {
98 | // This lets us open files from the runtime error overlay.
99 | app.use(errorOverlayMiddleware());
100 | // This service worker file is effectively a 'no-op' that will reset any
101 | // previous service worker registered for the same host:port combination.
102 | // We do this in development to avoid hitting the production cache if
103 | // it used the same host and port.
104 | // https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
105 | app.use(noopServiceWorkerMiddleware());
106 | },
107 | };
108 | };
109 |
--------------------------------------------------------------------------------
/src/elements/app-piano/songs/pgydyd.js:
--------------------------------------------------------------------------------
1 | // const song = [
2 | // '3', '4',
3 | // '5', '5', '5', '6', '7', '+1..',
4 | // '+1', '+1', '7', '+2', '6', '5', '5', '5', '+2', '+1', '+1', '+3', '+3..',
5 | // '+1', '+2', '+3', '+3', '+4', '+3', '+2', '+3', '+1', '+1', '6', '6',
6 | // '6', '7', '+1', '+2', '+2', '+1', '7', '6', '+4', '+2',
7 | // // 将愿望
8 | // '+2..', '3', '4', '5',
9 | // // 折飞机寄成信
10 | // '5', '5', '5', '6', '7', '+1..',
11 | // '+1', '+1', '7', '+2', '6', '5', '5', '5', '+2', '+1', '+1', '+3', '+3..',
12 | // '+1', '+2', '+3', '+3', '+4', '+3', '+2', '+3', '+1', '+1', '6', '6',
13 | // '6', '7', '+1', '+2', '+2', '+1', '7', '6', '+4', '+2..',
14 | // // 一起长大的约定
15 | // '3', '5', '+1', '+3', '+3.', '+4', '+2..', '+2', '+5', '7', '+1..',
16 | // '+3', '+4', '+5', '+1', '+1', '+2', '+3', '+3..',
17 | // // 说好要一起旅行
18 | // '3', '5', '+1.', '+3', '+3.', '+4', '+2..',
19 | // // 是你如今
20 | // '+2', '+5', '7', '+1..',
21 | // // 唯一坚持的任性
22 | // '+3', '+4', '+5', '+1', '+1', '+2.', '+1', '+1',
23 | // // '+2', '+1', '7', '+1', '+3', '+2', '+1', '+2', '+3', '+2', '+4', '+3', '+2', '+1', '7', '+1', '+2', '+1', '7', '+1', '+3', '+2', '+1', '+2', '+3', '+2', '+4', '+3', '+1..',
24 | // // 在走廊
25 | // '3', '4',
26 | // '5', '5', '5', '6', '7', '+1..',
27 | // '+1', '+1', '7', '+2', '6', '5', '5', '5', '+2', '+1', '+1', '+3', '+3..',
28 | // '+1', '+2', '+3', '+3', '+4', '+3', '+2', '+3', '+1', '+1', '6', '6',
29 | // '6', '7', '+1', '+2', '+2', '+1', '7', '6', '+4', '+2',
30 |
31 | // // 一起长大的约定
32 | // '3', '5', '+1', '+3', '+3.', '+4', '+2..', '+2', '+5', '7', '+1..',
33 | // '+3', '+4', '+5', '+1', '+1', '+2', '+3', '+3..',
34 | // // 说好要一起旅行
35 | // '3', '5', '+1.', '+3', '+3.', '+4', '+2..',
36 | // // 是你如今
37 | // '+2', '+5', '7', '+1..',
38 | // // 唯一坚持的任性
39 | // '+3', '+4', '+5', '+1', '+1', '+2.', '+1', '+1',
40 |
41 |
42 | // // 一起长大2
43 | // '+6', '+5', '+3', '+2', '+1', '+3.', '+4', '+2..',
44 | // '+6', '+5', '7', '+1..',
45 | // // 与你聊不完的曾经
46 | // '+3', '+4', '+5', '+1', '+1', '+2', '+3', '+3..',
47 | // // 而我已经分不清
48 | // '3', '5', '+1', '+3', '+3.', '+2', '+2', '+2..', '+2', '+5', '7', '+2', '+1', '+1',
49 | // // 还是错过的爱情
50 | // '+3', '+4', '+5', '+1', '+1', '+2.', '+1', '+1..'
51 | // ]
52 | // export default [...song]
53 |
54 | const song = [
55 | '3', '5', '+1', '+3', '+3.6.3.-4', '1', '3', '+4', '+274-4', '-7', '2',
56 | '+25-3', '-7', '+52', '7', '+13--6', '-3', '-1',
57 | '+3+14-2', '+4-6', '+51', '+1', '+164--5', '-4', '+22-7', '+353-1', '-5', '1', '2',
58 | '3-#6-3-1', '5', '+1#41--#4', '+33', '+3.6.3.--4', '-1', '-6', '+4', '+274--4', '-2', '-7',
59 | '+27--3', '--7', '+5-5', '7', '+13--6', '-3', '1',
60 | '+3+14-2', '+4-6', '+51', '+1', '+164--5', '-4', '+272-7', '+1#54-1', '-#5',
61 | '2', '4', '31-5-1', '3', '4', '5', '5-1', '5-5', '53', '5', '--7', '6-5', '72', '+1', '+1--6', '-3', '1',
62 | '+12-3--5', '7', '+22-7', '6', '6--4', '5-1', '5-6', '+2', '--5', '+1-2', '+3-7', '+3', '41-5-1',
63 | '31', '-5', '+13-#6-1', '+2', '+3#61', '+3', '+3-3--3', '+3-#5', '+42-7', '+3-3', '+3--#5', '+23-3', '#52', '+3',
64 | '+2.5.1.--6', '-3', '+1.', '-6', '-3', '641--4', '7-3', '+1-6', '+2-3', '+2--4', '+1-1', '+1-6-4', '+6', '+6--2', '+36--6', '+36-4-2', '+2'
65 | ]
66 |
67 | const song2 = [
68 | '+2--5', '-2', '1-5', '-7-5--5', '3', '4', '5---5', '5-1', '5-5', '52', '53', '5--7', '6-5', '7-7', '52', '+2', '+2--6', '+1-3', '1-5', '-3', '+12-3--5', '7', '+22-7', '6', '6--4', '5-1',
69 | '5-6', '+2--4', '+2--5', '+1-2', '+3-7', '+3-2', '+3-1', '-5', '31', '-5', '+1#63--1', '+2', '+3+11-#6-5', '+35-1', '+3--7---7', '+3-4', '+42-6', '+3---4', '---5', '+3--#5---#5', '+2-3', '2-7', '+3-3',
70 | '+2.5.1.--6---6', '-3', '+1.', '-6', '-3', '642--5', '7-2', '+15-6-4', '+2-2', '+2--2', '+1--6', '+14-4-2', '6--2', '--3', '--#4', '+3-2', '+362-4', '+2-2',
71 | '1-6-4.--5.', '1-6', '-4--5', '3-7-4--5', '5', '+14--5---5', '+3', '+3.6.--4', '-1', '-4', '+44-1', '+242--4', '-2', '-7', '-2', '+25--3', '--7', '+5-5-2', '7--7', '+13--6', '-3', '1', '-3', '+314--4', '+4-1', '+5+14-6', '+1--4', '+14-4--5', '-2', '+274-5', '--5',
72 | '+3+15--1', '-5', '2', '-5', '3-#6--1', '5', '+13---#4', '+3', '+3.6.--4', '-1', '-4', '+44-1', '+242--4', '-2', '-7', '-2', '+25--3', '--7', '+5-5-2', '7--7', '+13--6', '-3', '1', '-3', '+314--4', '+4-1', '+5+14-6', '+1--4', '+14-4--5', '-2', '+274-5', '--5',
73 | '+2-1--1', '2', '5-3', '+2', '+11-5', '3-3', '+1', '7-1--1', '2', '3-3', '7', '+11-5', '2', '3-3', '+1', '+2--6---6', '2', '5-3', '+2', '+11-5', '3', '5-3', '+1', '7--6---6', '2', '3-3', '+1', '+3--#4', '4', '6-2', '+3', '+22-#4', '4', '6-2', '+2', '+3--2', '4', '6--6', '+3', '+2-4-2', '4', '6--6', '+2', '+47--5', '4', '5-2', '+47', '+37-7-4', '4', '5-2', '+37',
74 | '+2-1--1', '2', '5-3', '+2', '+11-5', '3-3', '+1', '7-1--1', '2', '3-3', '7', '+11-5', '2', '3-3', '+1', '+2--6---6', '2', '5-3', '+2', '+11-5', '3', '5-3', '+1', '7--6---6', '2', '3-3', '+1', '+3--#4', '4', '6-2', '+3', '+22-#4', '4', '6-2', '+2', '+3--2', '4', '6--6', '+3', '+2-4-2', '4', '6--6',
75 | '--5---5',
76 | '51--1', '-1', '51', '-1', '51--1', '3', '4', '5', '+153-1--1', '-#6-5-3', '3-6-5-1--1', '53-6-1--1', '+6+3--#6--1', '+5', '+3--1', '+2-1', '+1--1', '+3.6.--4', '-1', '-4', '+44-1', '+242--4', '-2', '-7', '-2', '+6.', '+2.', '--3', '--7', '+5.7.', '-5-2', '7--7', '+13--6', '-3', '1', '-3',
77 | '+314', '+314--4', '+4-1', '+5+14-6', '+1--4', '+14-4--5', '-2', '+274-5', '--5',
78 | '+3+15--1', '-5', '2', '-5', '3-#6--1', '5', '+13---#4', '+3', '+3.6.--4', '-1', '-4', '+44-1', '+242--4', '-2', '-7', '-2', '+25--3', '--7', '+5-5-2', '7--7', '+13--6', '-3', '1', '-3', '+314--4', '+4-1', '+5+14-6', '+1--4', '+14-4--5', '-2', '+274-5',
79 | '+153--1', '31', '-5--1', '31--1', '5', '+1', '+2', '+3+15-4', '+4-6', '+51', '+13', '+1-5', '-7', '+22', '+1-6', '1', '3', '1', '-7-5', '51', '+13', '+21', '+315-4', '+41', '+53', '+1', '+1-5', '-7', '+22', '+1-1', '-5', '42', '-5', '31--1'
80 |
81 | ]
82 |
83 | export default [...song, ...song2]
--------------------------------------------------------------------------------
/src/elements/app-piano/songs/moon.js:
--------------------------------------------------------------------------------
1 | const song = [
2 | {// 月亮代表我的心
3 | // -5
4 | note: 'G3',
5 | time: 500
6 | }, {
7 | // 1.
8 | note: 'C4',
9 | time: 1000
10 | }, {
11 | // 3
12 | note: 'E4',
13 | time: 500
14 | }, {
15 | // 5.
16 | note: 'G4',
17 | time: 1000
18 | }, {
19 | // 1
20 | note: 'C4',
21 | time: 500
22 | }, {
23 | // -7
24 | note: 'B3',
25 | time: 1000
26 | }, {
27 | // 3
28 | note: 'E4',
29 | time: 500
30 | }, {
31 | // 5
32 | // 0
33 | // 我
34 | note: 'G4',
35 | time: 1500
36 | }, {
37 | // 5
38 | note: 'G4',
39 | time: 500
40 | }, {
41 | // 6.
42 | note: 'A4',
43 | time: 1000
44 | }, {
45 | // 7
46 | note: 'B4',
47 | time: 500
48 | }, {
49 | // +1.
50 | note: 'C5',
51 | time: 1000
52 | }, {
53 | // 6
54 | note: 'A4',
55 | time: 500
56 | }, {
57 | // 5
58 | note: 'G4',
59 | time: 1500
60 | }, {
61 | // 3
62 | note: 'E4',
63 | time: 500
64 | }, {
65 | // 2
66 | note: 'D4',
67 | time: 500
68 | }, {
69 | // 1.
70 | note: 'C4',
71 | time: 1000
72 | }, {
73 | // 1
74 | note: 'C4',
75 | time: 500
76 | }, {
77 | // 1
78 | note: 'C4',
79 | time: 500
80 | }, {
81 | // 3
82 | // 我的
83 | note: 'E4',
84 | time: 500
85 | }, {
86 | // 2
87 | note: 'D4',
88 | time: 500
89 | }, {
90 | // 1
91 | note: 'C4',
92 | time: 1000
93 | }, {
94 | // 1
95 | note: 'C4',
96 | time: 500
97 | }, {
98 | // 1
99 | note: 'C4',
100 | time: 500
101 | }, {
102 | // 2
103 | // 月亮
104 | note: 'D4',
105 | time: 500
106 | }, {
107 | // 3
108 | note: 'E4',
109 | time: 500
110 | }, {
111 | // 2
112 | note: 'D4',
113 | time: 1000
114 | }, {
115 | // 1
116 | note: 'C4',
117 | time: 500
118 | }, {
119 | // -6
120 | note: 'A3',
121 | time: 500
122 | }, {
123 | // 2
124 | note: 'D4',
125 | time: 500
126 | }, {
127 | // 3
128 | note: 'E4',
129 | time: 500
130 | }, {
131 | // 2
132 | note: 'D4',
133 | time: 1500
134 | }, {// 月亮代表我的心
135 | // -5
136 | note: 'G3',
137 | time: 500
138 | }, {
139 | // 1.
140 | note: 'C4',
141 | time: 1000
142 | }, {
143 | // 3
144 | note: 'E4',
145 | time: 500
146 | }, {
147 | // 5.
148 | note: 'G4',
149 | time: 1000
150 | }, {
151 | // 1
152 | note: 'C4',
153 | time: 500
154 | }, {
155 | // -7
156 | note: 'B3',
157 | time: 1000
158 | }, {
159 | // 3
160 | note: 'E4',
161 | time: 500
162 | }, {
163 | // 5
164 | // 0
165 | // 我
166 | note: 'G4',
167 | time: 1500
168 | }, {
169 | // 5
170 | note: 'G4',
171 | time: 500
172 | }, {
173 | // 6.
174 | note: 'A4',
175 | time: 1000
176 | }, {
177 | // 7
178 | note: 'B4',
179 | time: 500
180 | }, {
181 | // +1.
182 | note: 'C5',
183 | time: 1000
184 | }, {
185 | // 6
186 | note: 'A4',
187 | time: 500
188 | }, {
189 | // 5
190 | note: 'G4',
191 | time: 1500
192 | }, {
193 | // 3
194 | note: 'E4',
195 | time: 500
196 | }, {
197 | // 2
198 | note: 'D4',
199 | time: 500
200 | }, {
201 | // 1.
202 | note: 'C4',
203 | time: 1000
204 | }, {
205 | // 1
206 | note: 'C4',
207 | time: 500
208 | }, {
209 | // 1
210 | note: 'C4',
211 | time: 500
212 | }, {
213 | // 3
214 | // 我的
215 | note: 'E4',
216 | time: 500
217 | }, {
218 | // 2
219 | note: 'D4',
220 | time: 500
221 | }, {
222 | // 1
223 | note: 'C4',
224 | time: 1000
225 | }, {
226 | // 1
227 | note: 'C4',
228 | time: 500
229 | }, {
230 | // 1
231 | note: 'C4',
232 | time: 500
233 | }, {
234 | // 2
235 | // 月亮
236 | note: 'D4',
237 | time: 500
238 | }, {
239 | // 3
240 | note: 'E4',
241 | time: 500
242 | }, {
243 | // 2
244 | note: 'D4',
245 | time: 1000
246 | }, {
247 | // 1
248 | note: 'C4',
249 | time: 500
250 | }, {
251 | // -6
252 | note: 'A3',
253 | time: 500
254 | }, {
255 | // 2
256 | note: 'D4',
257 | time: 500
258 | }, {
259 | // 3
260 | note: 'E4',
261 | time: 500
262 | }, {
263 | // 2
264 | note: 'D4',
265 | time: 1500
266 | },
267 |
268 | {
269 | // 轻轻地一个吻
270 | // 3
271 | note: 'E4',
272 | time: 500
273 | }, {
274 | // 5
275 | note: 'G4',
276 | time: 500
277 | }, {
278 | // 3
279 | note: 'E4',
280 | time: 1000
281 | }, {
282 | // 2
283 | note: 'D4',
284 | time: 500
285 | }, {
286 | // 1
287 | note: 'C4',
288 | time: 500
289 | }, {
290 | // 5
291 | note: 'G4',
292 | time: 500
293 | }, {
294 | // -7
295 | note: 'B3',
296 | time: 1500
297 | }, {
298 | // -6
299 | note: 'A3',
300 | time: 500
301 | }, {
302 | // -7
303 | note: 'B3',
304 | time: 500
305 | }, {
306 | // -6
307 | note: 'A3',
308 | time: 1000
309 | }, {
310 | // -7
311 | note: 'B3',
312 | time: 500
313 | }, {
314 | // -6
315 | note: 'A3',
316 | time: 500
317 | }, {
318 | // -5
319 | note: 'G3',
320 | time: 500
321 | }, {
322 | // 3
323 | note: 'E4',
324 | time: 1500
325 | }, {
326 | // 5
327 | note: 'G4',
328 | time: 500
329 | }, {
330 | // 3.
331 | note: 'E4',
332 | time: 1000
333 | }, {
334 | // 2
335 | note: 'D4',
336 | time: 500
337 | }, {
338 | // 1
339 | note: 'C4',
340 | time: 500
341 | }, {
342 | // 5
343 | note: 'G4',
344 | time: 500
345 | }, {
346 | // -7...
347 | note: 'B3',
348 | time: 1500
349 | }, {
350 | // -6
351 | note: 'A3',
352 | time: 500
353 | }, {
354 | // -7
355 | note: 'B3',
356 | time: 500
357 | }, {
358 | // 1.
359 | note: 'C4',
360 | time: 1000
361 | }, {
362 | // 1
363 | note: 'C4',
364 | time: 500
365 | }, {
366 | // 1
367 | note: 'C4',
368 | time: 500
369 | }, {
370 | // 2
371 | note: 'D4',
372 | time: 500
373 | }, {
374 | // 3.
375 | note: 'E4',
376 | time: 500
377 | }, {
378 | // 2
379 | note: 'D4',
380 | time: 1500
381 | },
382 | // 你问我
383 | {
384 | // -5
385 | note: 'G3',
386 | time: 500
387 | }, {
388 | // 1.
389 | note: 'C4',
390 | time: 1000
391 | }, {
392 | // 3
393 | note: 'E4',
394 | time: 500
395 | }, {
396 | // 5.
397 | note: 'G4',
398 | time: 1000
399 | }, {
400 | // 1
401 | note: 'C4',
402 | time: 500
403 | }, {
404 | // -7
405 | note: 'B3',
406 | time: 1000
407 | }, {
408 | // 3
409 | note: 'E4',
410 | time: 500
411 | }, {
412 | // 5
413 | // 0
414 | // 我
415 | note: 'G4',
416 | time: 1500
417 | }, {
418 | // 5
419 | note: 'G4',
420 | time: 500
421 | }, {
422 | // 6.
423 | note: 'A4',
424 | time: 1000
425 | }, {
426 | // 7
427 | note: 'B4',
428 | time: 500
429 | }, {
430 | // +1.
431 | note: 'C5',
432 | time: 1000
433 | }, {
434 | // 6
435 | note: 'A4',
436 | time: 500
437 | }, {
438 | // 5
439 | note: 'G4',
440 | time: 1500
441 | }, {
442 | // 3
443 | note: 'E4',
444 | time: 500
445 | }, {
446 | // 2
447 | note: 'D4',
448 | time: 500
449 | }, {
450 | // 1.
451 | note: 'C4',
452 | time: 1000
453 | }, {
454 | // 1
455 | note: 'C4',
456 | time: 500
457 | }, {
458 | // 1
459 | note: 'C4',
460 | time: 500
461 | }, {
462 | // 3
463 | // 我的
464 | note: 'E4',
465 | time: 500
466 | }, {
467 | // 2
468 | note: 'D4',
469 | time: 500
470 | }, {
471 | // 1
472 | note: 'C4',
473 | time: 1000
474 | }, {
475 | // 1
476 | note: 'C4',
477 | time: 500
478 | }, {
479 | // 1
480 | note: 'C4',
481 | time: 500
482 | }, {
483 | // 2
484 | // 月亮
485 | note: 'D4',
486 | time: 500
487 | }, {
488 | // 3
489 | note: 'E4',
490 | time: 500
491 | }, {
492 | // 2
493 | note: 'D4',
494 | time: 1000
495 | }, {
496 | // -6
497 | note: 'A3',
498 | time: 1000
499 | }, {
500 | // -7
501 | note: 'B3',
502 | time: 500
503 | }, {
504 | // 1
505 | note: 'C4',
506 | time: 1000
507 | }, {
508 | // 2
509 | note: 'D4',
510 | time: 500
511 | }, {
512 | // 1
513 | note: 'C4',
514 | time: 1000
515 | }
516 | ]
517 | export default [...song, ...song]
--------------------------------------------------------------------------------
/config/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const autoprefixer = require('autoprefixer');
4 | const path = require('path');
5 | const webpack = require('webpack');
6 | const HtmlWebpackPlugin = require('html-webpack-plugin');
7 | const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
8 | const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
9 | const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
10 | const eslintFormatter = require('react-dev-utils/eslintFormatter');
11 | const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
12 | const getClientEnvironment = require('./env');
13 | const paths = require('./paths');
14 | const fileList = require('./entry');
15 |
16 | const pjson = require('../package.json');
17 |
18 | let entry = {};
19 | let htmlWebpackPlugins = [];
20 |
21 | fileList.forEach(function (item) {
22 | entry[item] = [
23 | require.resolve('./polyfills'),
24 | require.resolve('react-dev-utils/webpackHotDevClient'),
25 | paths.appSrc + '/' + item + '.js',
26 | ];
27 |
28 | htmlWebpackPlugins.push(
29 | new HtmlWebpackPlugin({
30 | inject: true,
31 | chunks: [item],
32 | template: paths.appHtml,
33 | filename: item + '.html'
34 | }));
35 | });
36 |
37 | // Webpack uses `publicPath` to determine where the app is being served from.
38 | // In development, we always serve from the root. This makes config easier.
39 | const publicPath = '/';
40 | // `publicUrl` is just like `publicPath`, but we will provide it to our app
41 | // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
42 | // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
43 | const publicUrl = '';
44 | // Get environment variables to inject into our app.
45 | const env = getClientEnvironment(publicUrl);
46 |
47 | // This is the development configuration.
48 | // It is focused on developer experience and fast rebuilds.
49 | // The production configuration is different and lives in a separate file.
50 | module.exports = {
51 | // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
52 | // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
53 | devtool: 'cheap-module-source-map',
54 | // These are the "entry points" to our application.
55 | // This means they will be the "root" imports that are included in JS bundle.
56 | // The first two entry points enable "hot" CSS and auto-refreshes for JS.
57 | entry: entry,
58 | output: {
59 | // Add /* filename */ comments to generated require()s in the output.
60 | pathinfo: true,
61 | path: paths.appBuild,
62 | // This does not produce a real file. It's just the virtual path that is
63 | // served by WebpackDevServer in development. This is the JS bundle
64 | // containing code from all our entry points, and the Webpack runtime.
65 | filename: 'static/js/[name].bundle.js',
66 | // There are also additional JS chunk files if you use code splitting.
67 | chunkFilename: 'static/js/[name].chunk.js',
68 | // This is the URL that app is served from. We use "/" in development.
69 | publicPath: publicPath,
70 | // Point sourcemap entries to original disk location (format as URL on Windows)
71 | devtoolModuleFilenameTemplate: info =>
72 | path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
73 | },
74 | resolve: {
75 | // This allows you to set a fallback for where Webpack should look for modules.
76 | // We placed these paths second because we want `node_modules` to "win"
77 | // if there are any conflicts. This matches Node resolution mechanism.
78 | // https://github.com/facebookincubator/create-react-app/issues/253
79 | modules: ['node_modules', paths.appNodeModules].concat(
80 | // It is guaranteed to exist because we tweak it in `env.js`
81 | process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
82 | ),
83 | // These are the reasonable defaults supported by the Node ecosystem.
84 | // We also include JSX as a common component filename extension to support
85 | // some tools, although we do not recommend using it, see:
86 | // https://github.com/facebookincubator/create-react-app/issues/290
87 | // `web` extension prefixes have been added for better support
88 | // for React Native Web.
89 | extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
90 | alias: pjson.alias,
91 | plugins: [
92 | // Prevents users from importing files from outside of src/ (or node_modules/).
93 | // This often causes confusion because we only process files within src/ with babel.
94 | // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
95 | // please link the files into your node_modules/ and let module-resolution kick in.
96 | // Make sure your source files are compiled, as they will not be processed in any way.
97 | new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
98 | ],
99 | },
100 | module: {
101 | strictExportPresence: true,
102 | rules: [
103 | // TODO: Disable require.ensure as it's not a standard language feature.
104 | // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
105 | // { parser: { requireEnsure: false } },
106 |
107 | // First, run the linter.
108 | // It's important to do this before Babel processes the JS.
109 | // {
110 | // test: /\.(js|jsx|mjs)$/,
111 | // enforce: 'pre',
112 | // use: [
113 | // {
114 | // options: {
115 | // formatter: eslintFormatter,
116 | // eslintPath: require.resolve('eslint'),
117 |
118 | // },
119 | // loader: require.resolve('eslint-loader'),
120 | // },
121 | // ],
122 | // include: paths.appSrc,
123 | // },
124 | {
125 | // "oneOf" will traverse all following loaders until one will
126 | // match the requirements. When no loader matches it will fall
127 | // back to the "file" loader at the end of the loader list.
128 | oneOf: [
129 | // "url" loader works like "file" loader except that it embeds assets
130 | // smaller than specified limit in bytes as data URLs to avoid requests.
131 | // A missing `test` is equivalent to a match.
132 | {
133 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
134 | loader: require.resolve('url-loader'),
135 | options: {
136 | limit: 10000,
137 | name: 'static/media/[name].[hash:8].[ext]',
138 | },
139 | },
140 | // Process JS with Babel.
141 | {
142 | test: /\.(js|jsx|mjs)$/,
143 | //include: paths.appSrc,
144 | loader: require.resolve('babel-loader'),
145 | options: {
146 | "presets": [
147 | "@babel/preset-env",
148 | [
149 | "@babel/preset-react",
150 | {
151 | "pragma": "Omi.h"
152 | }
153 | ]
154 | ],
155 | "plugins": [
156 | "@babel/plugin-proposal-class-properties",
157 | [
158 | "@babel/plugin-proposal-decorators",
159 | {
160 | "legacy": true
161 | }
162 | ],
163 | "@babel/plugin-proposal-function-bind",
164 | "@babel/plugin-proposal-object-rest-spread",
165 | "@babel/plugin-syntax-dynamic-import"
166 | ],
167 | // This is a feature of `babel-loader` for webpack (not Babel itself).
168 | // It enables caching results in ./node_modules/.cache/babel-loader/
169 | // directory for faster rebuilds.
170 | cacheDirectory: true,
171 | },
172 | },
173 | {
174 | test: /[\\|\/]_[\S]*\.css$/,
175 | use: [
176 | 'to-string-loader',
177 | 'css-loader'
178 | ]
179 | },
180 | {
181 | test: /[\\|\/]_[\S]*\.less$/,
182 | use: [
183 | 'to-string-loader',
184 | 'css-loader',
185 | 'less-loader'
186 | ]
187 | },
188 | {
189 | test: /\.less$/,
190 | use: [
191 | 'style-loader',
192 | 'css-loader',
193 | 'less-loader'
194 | ]
195 | },
196 | // "postcss" loader applies autoprefixer to our CSS.
197 | // "css" loader resolves paths in CSS and adds assets as dependencies.
198 | // "style" loader turns CSS into JS modules that inject
--------------------------------------------------------------------------------
/src/elements/app-piano/index.js:
--------------------------------------------------------------------------------
1 | import { WeElement, define, h } from "omi";
2 | import notes from "./notes.js";
3 | import moon from "./songs/moon.js";
4 | import fuji from "./songs/fuji.js";
5 | import later from "./songs/later.js";
6 | import pgydyd from "./songs/pgydyd.js";
7 | import xxy from "./songs/xxy.js";
8 | import pianoKeys from "./pianoKeys.js";
9 |
10 | class AppPiano extends WeElement {
11 | constructor(...args) {
12 | super(...args);
13 |
14 | this.add = () => this.store.add();
15 |
16 | this.sub = () => this.store.sub();
17 |
18 | this.setSong = song => this.store.setSong(song);
19 |
20 | this.setCount = count => this.store.setSong(count);
21 | }
22 |
23 | render(props) {
24 | return h(
25 | "div",
26 | {
27 | class: ""
28 | },
29 | h(
30 | "div",
31 | {
32 | class: "piano"
33 | },
34 | this.data.pianoKeys.map(item => {
35 | return h(
36 | "div",
37 | {
38 | class: "piano-key"
39 | },
40 | h(
41 | "div",
42 | {
43 | "data-type": "white",
44 | ref: e => {
45 | this[item.white.name] = e;
46 | },
47 | class: "piano-key__white",
48 | onClick: this.playNote.bind(this, item.white.name),
49 | "data-key": item.white.keyCode,
50 | "data-note": item.white.name
51 | },
52 | h(
53 | "span",
54 | {
55 | class: "piano-note"
56 | },
57 | item.white.name
58 | ),
59 | h("audio", {
60 | preload: "auto",
61 | src: this.data.notes[item.white.name].url,
62 | hidden: "true",
63 | "data-note": item.white.name,
64 | class: "audioEle"
65 | })
66 | ),
67 | h(
68 | "div",
69 | {
70 | "data-type": "black",
71 | ref: e => {
72 | this[item.black.name] = e;
73 | },
74 | style: {
75 | display: item.black.name ? "block" : "none"
76 | },
77 | class: "piano-key__black",
78 | onClick: this.playNote.bind(this, item.black.name),
79 | "data-key": item.black.keyCode,
80 | "data-note": item.black.name
81 | },
82 | h(
83 | "span",
84 | {
85 | class: "piano-note",
86 | style: "color:#fff"
87 | },
88 | item.black.name
89 | ),
90 | h("audio", {
91 | preload: "auto",
92 | src:
93 | this.data.notes[item.black.name] &&
94 | this.data.notes[item.black.name].url,
95 | hidden: "true",
96 | "data-note": item.black.name,
97 | class: "audioEle"
98 | })
99 | )
100 | );
101 | })
102 | ),
103 | h(
104 | "div",
105 | {
106 | class: "text-center"
107 | },
108 | h(
109 | "p",
110 | null,
111 | "Click the button below to let the piano play the song automatically:"
112 | ),
113 | h(
114 | "p",
115 | null,
116 | "\u70B9\u51FB\u4E0B\u9762\u6309\u94AE\u8BA9\u94A2\u7434\u81EA\u52A8\u6F14\u594F\u6B4C\u66F2:",
117 | this.store.data.count > 0 ? "1" : "0"
118 | ),
119 | h(
120 | "div",
121 | null,
122 | this.store.data.count > 0
123 | ? h(
124 | "button",
125 | {
126 | onClick: this.stopSong.bind(this),
127 | class: "btn btn-outline-info btn-stop"
128 | },
129 | "Stop & \u6682\u505C"
130 | )
131 | : h(
132 | "div",
133 | null,
134 | h(
135 | "button",
136 | {
137 | onClick: this.playSong.bind(this, moon),
138 | class: "btn btn-outline-info"
139 | },
140 | "\u6708\u4EAE\u4EE3\u8868\u6211\u7684\u5FC3"
141 | ),
142 | h(
143 | "button",
144 | {
145 | onClick: this.playSong.bind(this, pgydyd),
146 | class: "btn btn-outline-info"
147 | },
148 | "\u84B2\u516C\u82F1\u7684\u7EA6\u5B9A"
149 | ),
150 | h(
151 | "button",
152 | {
153 | onClick: this.playSong.bind(this, xxy),
154 | class: "btn btn-outline-info"
155 | },
156 | "\u5C0F\u5E78\u8FD0"
157 | ),
158 | h(
159 | "button",
160 | {
161 | onClick: this.playSong.bind(this, fuji),
162 | class: "btn btn-outline-info"
163 | },
164 | "\u5BCC\u58EB\u5C71\u4E0B&\u7231\u60C5\u8F6C\u79FB"
165 | )
166 | )
167 | )
168 | )
169 | );
170 | }
171 |
172 | install() {
173 | this.data = {
174 | notes,
175 | pianoKeys
176 | };
177 |
178 | document.onkeydown = event => {
179 | var e = event || window.event || arguments.callee.caller.arguments[0];
180 |
181 | let playNote = key => {
182 | if (e.shiftKey === true) {
183 | this.playNote(`${key}2`);
184 | } else if (e.altKey === true) {
185 | this.playNote(`${key}5`);
186 | } else if (e.ctrlKey === true) {
187 | this.playNote(`${key}3`);
188 | } else if (e.metaKey === true) {
189 | this.playNote(`${key}6`);
190 | e.returnValue = false;
191 | } else {
192 | this.playNote(`${key}4`);
193 | }
194 | };
195 |
196 | if (e && 49 <= e.keyCode && e.keyCode <= 55) {
197 | switch (e.keyCode) {
198 | case 49:
199 | playNote("C");
200 | break;
201 |
202 | case 50:
203 | playNote("D");
204 | break;
205 |
206 | case 51:
207 | playNote("E");
208 | break;
209 |
210 | case 52:
211 | playNote("F");
212 | break;
213 |
214 | case 53:
215 | playNote("G");
216 | break;
217 |
218 | case 54:
219 | playNote("A");
220 | break;
221 |
222 | case 55:
223 | playNote("B");
224 | break;
225 | }
226 | }
227 |
228 | if (
229 | e &&
230 | (81 === e.keyCode ||
231 | e.keyCode === 87 ||
232 | e.keyCode === 69 ||
233 | e.keyCode === 82 ||
234 | e.keyCode === 84)
235 | ) {
236 | switch (e.keyCode) {
237 | case 81:
238 | playNote("C#");
239 | break;
240 |
241 | case 87:
242 | playNote("D#");
243 | break;
244 |
245 | case 69:
246 | playNote("F#");
247 | break;
248 |
249 | case 82:
250 | playNote("G#");
251 | break;
252 |
253 | case 84:
254 | playNote("A#");
255 | break;
256 | }
257 | }
258 | };
259 | }
260 |
261 | stopSong() {
262 | clearTimeout(this.timer);
263 | this.store.data.song = [];
264 | this.store.data.count = 0;
265 | console.log("reset");
266 | }
267 |
268 | playNote(name) {
269 | console.log(this.data.notes[name]);
270 |
271 | if (!this.data.notes[name]["isPlay"]) {
272 | let audio = this[name].childNodes[1];
273 | this[
274 | name
275 | ].style.background = `linear-gradient(-20deg, #3330fb, #000, #222)`;
276 | let timer = setTimeout(() => {
277 | this[name].getAttribute("data-type") === "white"
278 | ? (this[
279 | name
280 | ].style.background = `linear-gradient(-30deg, #f8f8f8, #fff)`)
281 | : (this[
282 | name
283 | ].style.background = `linear-gradient(-20deg, #222, #000, #222)`);
284 | clearTimeout(timer);
285 | }, 1000);
286 | audio.currentTime = 0;
287 | audio.play();
288 | this.data.notes[name]["isPlay"] = true;
289 | let isPlay = setTimeout(() => {
290 | this.data.notes[name]["isPlay"] = false;
291 | clearTimeout(isPlay);
292 | }, 500);
293 | }
294 | }
295 |
296 | playSong(song) {
297 | this.setSong([...song]);
298 | let offset = 0;
299 | let time = 0;
300 |
301 | let playSong = async () => {
302 | if (offset < song.length && this.store.data.song.length > 0) {
303 | switch (typeof song[offset]) {
304 | case "string":
305 | let letters = song[offset].match(/[0-9]/g);
306 |
307 | switch (letters.length) {
308 | case 1:
309 | time = this.handleString(song, offset);
310 | break;
311 |
312 | default:
313 | time = this.handleStrings(song, offset);
314 | break;
315 | }
316 |
317 | break;
318 |
319 | case "object":
320 | console.log(song[offset]["note"]);
321 | time = song[offset]["time"];
322 | this.playNote(song[offset]["note"]);
323 | break;
324 |
325 | case "number":
326 | switch (song[offset]) {
327 | case 0:
328 | time = 1000;
329 | break;
330 | }
331 |
332 | break;
333 | }
334 |
335 | await new Promise(resolve => {
336 | let timer = setTimeout(() => {
337 | clearInterval(timer);
338 | resolve();
339 | }, time);
340 | });
341 | offset++;
342 | this.update();
343 | this.add();
344 | playSong();
345 | } else {
346 | clearTimeout(this.timer);
347 | this.store.data.song = [];
348 | this.store.data.count = 0;
349 | return;
350 | }
351 | };
352 |
353 | playSong();
354 | }
355 |
356 | playSongByInterval(song) {
357 | clearInterval(this.interval);
358 | let offset = 0;
359 | let time = 0;
360 | this.interval = setInterval(() => {
361 | if (offset < song.length) {
362 | switch (typeof song[offset]) {
363 | case "string":
364 | let letters = song[offset].match(/[0-9]/g);
365 |
366 | switch (letters.length) {
367 | case 1:
368 | time = this.handleString(song, offset);
369 | break;
370 |
371 | default:
372 | time = this.handleStrings(song, offset);
373 | break;
374 | }
375 |
376 | break;
377 |
378 | case "object":
379 | console.log(song[offset]["note"]);
380 | time = song[offset]["time"];
381 | this.playNote(song[offset]["note"]);
382 | break;
383 |
384 | case "number":
385 | switch (song[offset]) {
386 | case 0:
387 | time = 1000;
388 | break;
389 | }
390 |
391 | break;
392 | }
393 |
394 | ++offset;
395 | } else {
396 | clearInterval(this.interval);
397 | }
398 | }, 500);
399 | }
400 |
401 | handleStrings(song, offset) {
402 | let reg = /[0-9]/g;
403 | let str = song[offset];
404 | let order = 1;
405 | let result = [];
406 |
407 | while (true) {
408 | let temp = reg.exec(str);
409 |
410 | if (temp) {
411 | result.push({
412 | text: temp[0],
413 | index: temp.index,
414 | order: order
415 | });
416 | order++;
417 | } else {
418 | break;
419 | }
420 | }
421 |
422 | result.map(item => {
423 | switch (str[item.index - 1]) {
424 | case "1":
425 | case "2":
426 | case "3":
427 | case "4":
428 | case "5":
429 | case "6":
430 | case "7":
431 | break;
432 |
433 | case "+":
434 | item.text = `+${item.text}`;
435 |
436 | switch (str[item.index - 2]) {
437 | case "+":
438 | item.text = `+${item.text}`;
439 | break;
440 | }
441 |
442 | break;
443 |
444 | case "-":
445 | item.text = `-${item.text}`;
446 |
447 | switch (str[item.index - 2]) {
448 | case "-":
449 | item.text = `-${item.text}`;
450 | break;
451 | }
452 |
453 | break;
454 |
455 | case "#":
456 | item.text = `#${item.text}`;
457 |
458 | switch (str[item.index - 2]) {
459 | case "-":
460 | item.text = `-${item.text}`;
461 |
462 | switch (str[item.index - 3]) {
463 | case "-":
464 | item.text = `-${item.text}`;
465 | break;
466 | }
467 |
468 | break;
469 |
470 | case "+":
471 | item.text = `+${item.text}`;
472 |
473 | switch (str[item.index - 3]) {
474 | case "+":
475 | item.text = `+${item.text}`;
476 | break;
477 | }
478 |
479 | break;
480 | }
481 |
482 | break;
483 | }
484 |
485 | switch (str[item.index + 1]) {
486 | case ".":
487 | item.text = `${item.text}.`;
488 |
489 | switch (str[item.index + 2]) {
490 | case ".":
491 | item.text = `${item.text}.`;
492 | break;
493 | }
494 |
495 | break;
496 | }
497 | });
498 | let notes = result.map(item => {
499 | return item.text;
500 | });
501 | let time = [];
502 | notes.forEach((item, index) => {
503 | time.push(this.handleString(notes, index));
504 | });
505 | return time.sort()[0];
506 | }
507 |
508 | handleString(song, offset) {
509 | let letter = song[offset].match(/[0-9]/g)[0];
510 | let subKey = song[offset].split("-").length - 1;
511 | let addKey = song[offset].split("+").length - 1;
512 | let pointKey = song[offset].split(".").length - 1;
513 | let halfKey = song[offset].split("#").length - 1;
514 | let note;
515 | let key;
516 | let time;
517 |
518 | switch (letter) {
519 | case "0":
520 | return (time = 1000);
521 | break;
522 |
523 | case "1":
524 | note = "C";
525 | break;
526 |
527 | case "2":
528 | note = "D";
529 | break;
530 |
531 | case "3":
532 | note = "E";
533 | break;
534 |
535 | case "4":
536 | note = "F";
537 | break;
538 |
539 | case "5":
540 | note = "G";
541 | break;
542 |
543 | case "6":
544 | note = "A";
545 | break;
546 |
547 | case "7":
548 | note = "B";
549 | break;
550 | }
551 |
552 | switch (subKey) {
553 | case 0:
554 | key = 4;
555 | break;
556 |
557 | case 1:
558 | key = 3;
559 | break;
560 |
561 | case 2:
562 | key = 2;
563 | break;
564 | }
565 |
566 | switch (addKey) {
567 | case 0:
568 | key = 4;
569 | break;
570 |
571 | case 1:
572 | key = 5;
573 | break;
574 |
575 | case 2:
576 | key = 6;
577 | break;
578 | }
579 |
580 | switch (pointKey) {
581 | case 0:
582 | time = 500;
583 | break;
584 |
585 | case 1:
586 | time = 1000;
587 | break;
588 |
589 | case 2:
590 | time = 1500;
591 | break;
592 | }
593 |
594 | console.log(`${note + (halfKey > 0 ? "#" : "") + key}`);
595 | this.playNote(`${note + (halfKey > 0 ? "#" : "") + key}`);
596 | return time;
597 | }
598 |
599 | recordSong() {}
600 | }
601 |
602 | AppPiano.css = `
603 | * {
604 | margin: 0;
605 | padding: 0;
606 | }
607 |
608 | .icon {
609 | width: 24px;
610 | }
611 |
612 | .piano {
613 | margin: 0 200px;
614 | background: linear-gradient(-65deg, #000, #222, #000, #666, #222 75%);
615 | border-top: .8rem solid #282828;
616 | -webkit-box-shadow: inset 0 -1px 1px hsla(0, 0%, 100%, .5), inset -0.4rem 0.4rem #282828;
617 | box-shadow: inset 0 -1px 1px hsla(0, 0%, 100%, .5), inset -0.4rem 0.4rem #282828;
618 | display: -webkit-box;
619 | display: -ms-flexbox;
620 | display: flex;
621 | height: 80vh;
622 | height: 20vh;
623 | -webkit-box-pack: center;
624 | -ms-flex-pack: center;
625 | justify-content: center;
626 | overflow: hidden;
627 | padding-bottom: 2%;
628 | padding-left: 2.5%;
629 | padding-right: 2.5%;
630 | }
631 |
632 | @media screen and (max-width: 1000px) {
633 |
634 | /*当屏幕尺寸小于600px时,应用下面的CSS样式*/
635 | .piano {
636 | margin: 0 10px;
637 | }
638 | }
639 |
640 | .piano-key {
641 | color: blue;
642 | -webkit-box-flex: 1;
643 | -ms-flex: 1;
644 | flex: 1;
645 | margin: 0 .1rem;
646 | max-width: 8.8rem;
647 | position: relative;
648 | }
649 |
650 | .piano-key__white {
651 | display: flex;
652 | flex-direction: column-reverse;
653 | background: linear-gradient(-30deg, #f8f8f8, #fff);
654 | -webkit-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);
655 | 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);
656 | height: 100%;
657 | position: relative;
658 | }
659 |
660 | .piano-key__black {
661 | display: flex;
662 | flex-direction: column-reverse;
663 | background: linear-gradient(-20deg, #222, #000, #222);
664 | -webkit-box-shadow: inset 0 -1px 2px hsla(0, 0%, 100%, .4), 0 2px 3px rgba(0, 0, 0, .4);
665 | box-shadow: inset 0 -1px 2px hsla(0, 0%, 100%, .4), 0 2px 3px rgba(0, 0, 0, .4);
666 | border-width: .2rem .4rem 1.2rem;
667 | border-style: solid;
668 | border-color: #666 #222 #111 #555;
669 | height: 60%;
670 | left: 100%;
671 | position: absolute;
672 | -webkit-transform: translateX(-50%);
673 | transform: translateX(-50%);
674 | top: 0;
675 | width: 70%;
676 | z-index: 1;
677 | }
678 |
679 | .piano-note {
680 | color: #000;
681 | /* 隐藏音符显示 */
682 | /* font-size: 8px; */
683 | font-size: 0px;
684 | text-align: center;
685 | height: 20px;
686 | }
687 |
688 | a {
689 | text-decoration: none;
690 | }
691 |
692 | .text-center {
693 | margin: 15px;
694 | text-align: center !important;
695 | }
696 |
697 | /* .btn:not(:disabled):not(.disabled) {
698 | cursor: pointer;
699 | } */
700 |
701 | .btn-outline-info {
702 | color: #17a2b8;
703 | background-color: transparent;
704 | background-image: none;
705 | border-color: #17a2b8;
706 |
707 | }
708 |
709 | .btn {
710 | text-transform: none;
711 | margin: 15px;
712 | display: inline-block;
713 | font-weight: 400;
714 | text-align: center;
715 | /* white-space: nowrap; */
716 | vertical-align: middle;
717 | /* -webkit-user-select: none;
718 | -moz-user-select: none;
719 | -ms-user-select: none;
720 | user-select: none; */
721 | border: 1px solid #17a2b8;
722 | padding: 8px 8px;
723 | font-size: 16px;
724 | line-height: 16px;
725 | border-radius: 2.5px;
726 | /* transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; */
727 | }
728 |
729 | .btn-stop {
730 | color: #ff7171;
731 | border-color: #ff7171;
732 | }
733 | `;
734 | AppPiano.use = [
735 | {
736 | count: "count",
737 | song: "song"
738 | }
739 | ];
740 | define("app-piano", AppPiano);
741 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Omi Piano
4 |
5 |
6 |
7 | Build piano with Omi and Omi Snippets
8 |
9 | 基于Omi和Omi Snippets构建钢琴应用
10 |
11 |
12 |
13 |
14 | Made with ❤︎ by
15 | Eno Yao
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
33 | # Usage
34 |
35 |
36 |
37 | > 体验地址: http://wscats.gitee.io/piano/build/ 或者 https://wscats.github.io/piano/build/
38 |
39 |
40 | > 项目地址: https://github.com/Wscats/piano
41 |
42 |
43 |
44 |
45 |
46 | 用键盘8个键演奏一首蒲公英的约定送给996的自己或者一首月亮代表我的心给七夕的她,非常简单~
47 |
48 | 这个项目仅仅用了几个简单的前端技术实现,献给每一位挚爱音乐的代码家🎹
49 |
50 | 如果你喜欢或者对你有帮助,给我点个赞支持下吧😊
51 |
52 | # Develop & Installation
53 |
54 |
55 | 开发,构建和运行。
56 |
57 | ```bash
58 | # 获取远程仓库代码
59 | git clone https://github.com/Wscats/piano
60 | # 进入目录
61 | cd piano
62 | # 安装依赖
63 | npm install
64 | # 启动项目
65 | npm start
66 | # 在浏览器访问 http://localhost:3000
67 | ```
68 |
69 | 使用 npm 包管理器安装。
70 |
71 | ```bash
72 | npm install omi-piano
73 | ```
74 |
75 | 运行或者发布属于自己的演奏版本。
76 |
77 | ```bash
78 | # 进入目录
79 | cd omi-piano
80 | # 安装依赖
81 | npm install
82 | # 启动项目
83 | npm start
84 | # 发布自已的演奏版本
85 | npm run build
86 | ```
87 |
88 | # 技术点和目录结构
89 |
90 | 项目中没有使用市面主流的框架(React,Vue 和 Angular )和热门的技术,而用的是 Omi 框架(`JSX+WebComponents`),还有 `Omil` 的单文件组件 `SFCs` 加载器,组件通讯基于`Proxy`特性,并结合了 VScode 的插件 `Eno-Snippets`基于`AST`和`正则`实时编译`.eno或.omi` 后缀组件减轻部分的 `Webpack` 的局部编译压力,当然其他同学们熟知的技术这里就不提及了。
91 |
92 |
93 |
94 |
95 |
96 | - src
97 | - assets
98 | - element
99 | - app-piano
100 | - songs 钢琴简谱目录
101 | - app-piano.eno 单文件组件
102 | - app-piano.js 组件编译后的JS文件
103 | - notes.js 键盘按键和音符的映射
104 | - index.js 组件根容器,配置`Proxy`的通信方法
105 | - public
106 | - samples/piano 钢琴单音符素材
107 |
108 | |app-piano.eno|开发中你需要编写的单文件组件|
109 | |-|-|
110 | |app-piano.js|经过`Eno-Snippets`修改或者保存文件`Hello.eno`后经过插件转化的`js`文件|
111 |
112 | 如右图,左边的代码是我们编写的 `.eno` 后缀的单文件组件,右边是经过 `Eno Snippets` 生成的 `.js` 后缀文件。
113 |
114 |
115 | # 简单乐理知识
116 |
117 | 首先我们先补习点音乐基础,提前收集好最基本的[钢琴单音素材](https://github.com/Wscats/piano/tree/master/public/samples/piano),每个音符对应一份`.mp3`文件,用一个对象记录起来,类似下面这样,举个例子这里的`A`指的是`CDEFGAB`音名中`A`也就是`La`,这是最基本的乐理,有没有让你想起小时候上音乐课,画板上的五线谱。
118 |
119 | ```js
120 | export default {
121 | A2: "./samples/piano/a54.mp3",
122 | A3: "./samples/piano/a69.mp3",
123 | A4: "./samples/piano/a80.mp3",
124 | A5: "./samples/piano/a74.mp3",
125 | A6: "./samples/piano/a66.mp3",
126 | 'A#3': "./samples/piano/b69.mp3",
127 | 'A#4': "./samples/piano/b80.mp3",
128 | 'A#5': "./samples/piano/b74.mp3",
129 | 'A#6': "./samples/piano/b66.mp3",
130 | // other...
131 | }
132 | ```
133 |
134 | 当然这里我们使用数字来等价替代,降低初学者的难度,看下表`1`等价于`C`中音也就是`Do`,由于很多歌都会用到钢琴更密集的中间部分按键,所以我们默认中音对应数字键:
135 |
136 | > `1 === C4 === Do`
137 |
138 |
139 | |数字键|1|2|3|4|5|6|7|
140 | |-|-|-|-|-|-|-|-|
141 | |音名|C4|D4|E4|F4|G4|A4|B4|
142 | |音符|Do|Re|Mi|Fa|Sol|La|Si|
143 |
144 |
145 |
146 | 这里专门制作一张图方便我们理解,请看右图:
147 |
148 | 当然实际情况还有全音和半音的区分,比如`A`的半音就是`A#`,还有中音,高音和倍高音,我们这里用`A4`表示中音,`A5`表示高音,`A6`表示倍高音,所以表格可以继续整理得更清晰,当我们要弹奏中音`A4`,只需要按键盘上的数字键`6`,如果要弹奏高音`A5`,只需要用组合键`Option+6`,我们只需要举一反三,就可以知道每个音符对应的键盘按键。
149 |
150 | |倍低音|C2|D2|E2|F2|G2|A2|B2|
151 | |-|-|-|-|-|-|-|-|
152 | |Shift键+(1-7)|Shift+1|Shift+2|Shift+3|Shift+4|Shift+5|Shift+6|Shift+7|
153 | |低音|C3|D3|E3|F3|G3|A3|B3|
154 | |Ctrl键+(1-7)|Ctrl+1|Ctrl+2|Ctrl+3|Ctrl+4|Ctrl+5|Ctrl+6|Ctrl+7|
155 | |中音|C4|D4|E4|F4|G4|A4|B4|
156 | |数字键1-7|1|2|3|4|5|6|7|
157 | |高音|C5|D5|E5|F5|G5|A5|B5|
158 | |Option键+(1-7)|Option+1|Option+2|Option+3|Option+4|Option+5|Option+6|Option+7|
159 | |倍高音|C6|D6|E6|F6|G6|A6|B6|
160 | |Command键+(1-7)|Command+1|Command+2|Command+3|Command+4|Command+5|Command+6|Command+7|
161 | |音符|Do|Re|Mi|Fa|Sol|La|Si|
162 |
163 | 上面是全音表,这里附上半音表:
164 |
165 | |倍低半音|C#2|D#2|F#2|G#2|A#2|
166 | |-|-|-|-|-|-|
167 | |Shift+|Shift+q|Shift+w|Shift+e|Shift+r|Shift+t|
168 | |低半音|C#3|D#3|F#3|G#3|A#3|
169 | |Ctrl+|Ctrl+q|Ctrl+w|Ctrl+e|Ctrl+r|Ctrl+t|
170 | |中半音|C#4|D#4|F#4|G#4|A#4|
171 | |字母键|q|w|e|r|t|
172 | |高半音|C#5|D#5|F#5|G#5|A#5|
173 | |Option+|Option+q|Option+w|Option+e|Option+r|Option+t|
174 | |倍高半音|C#6|D#6|F#6|G#6|A#6|
175 | |Command+|Command+q|Command+w|Command+e|Command+r|Command+t|
176 |
177 | 那么我们现在只需要用键盘上的5个`字母键(q,w,e,r,t)` + 4个`功能键(Shift,Control,Option和Command)` + 7个`数字键(1,2,3,4,5,6,7)`总共16个键,演奏钢琴60个单音(35个全音+25个半音),实际情况一首简单的钢琴曲可以不需要用到那么多,用几个简单的和弦即可。
178 |
179 | # 构建钢琴界面
180 |
181 | 有上面的前期准备,下面就是转化为我们的编程知识了,我们需要使用 HTML 来绘制我们的钢琴界面,我们可以参考 [codepen](https://codepen.io/search/pens?q=piano&page=1&order=popularity&depth=everything) 和 [codesandbox](https://codesandbox.io/search?query=piano&page=1&configure%5BhitsPerPage%5D=12) 的素材,这里我用了 `flex` 布局配合阴影和过度实现钢琴的黑白键,里面用了 React 的 JSX 语法去遍历渲染黑白键。
182 |
183 |
184 |
185 | ```html
186 |
187 | {this.data.pianoKeys.map((item)=>{return(
188 |
189 |
{ this[item.white.name] = e }} class="piano-key__white"
190 | onClick={this.playNote.bind(this,item.white.name)} data-key={item.white.keyCode}
191 | data-note={item.white.name}>
192 |
{item.white.name}
193 |
195 |
196 |
{ this[item.black.name] = e }} style={{
197 | display: item.black.name ? 'block' : 'none'
198 | }} class="piano-key__black" onClick={this.playNote.bind(this,item.black.name)} data-key={item.black.keyCode}
199 | data-note={item.black.name}>
200 |
{item.black.name}
201 |
203 |
204 |
205 | )})}
206 |
207 | ```
208 |
209 | 可以观察 CSS 的源代码,分别对应写黑键和白键的样式,还可以另外写多一个样式,用于键盘或者鼠标点击琴键时候的效果,可以简单给它加一个背景色即可,整体实现不会太复杂,具体可以调整样式的参数来打造属于自己的钢琴风格。
210 |
211 | ```css
212 | .piano {
213 | margin: 0 200px;
214 | background: linear-gradient(-65deg, #000, #222, #000, #666, #222 75%);
215 | border-top: .8rem solid #282828;
216 | box-shadow: inset 0 -1px 1px hsla(0, 0%, 100%, .5), inset -0.4rem 0.4rem #282828;
217 | display: flex;
218 | height: 80vh;
219 | height: 20vh;
220 | justify-content: center;
221 | overflow: hidden;
222 | padding-bottom: 2%;
223 | padding-left: 2.5%;
224 | padding-right: 2.5%;
225 | }
226 | .piano-key {
227 | color: blue;
228 | flex: 1;
229 | margin: 0 .1rem;
230 | max-width: 8.8rem;
231 | position: relative;
232 | }
233 |
234 | .piano-key__white {
235 | display: flex;
236 | flex-direction: column-reverse;
237 | background: linear-gradient(-30deg, #f8f8f8, #fff);
238 | 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);
239 | height: 100%;
240 | position: relative;
241 | }
242 |
243 | .piano-key__black {
244 | display: flex;
245 | flex-direction: column-reverse;
246 | background: linear-gradient(-20deg, #222, #000, #222);
247 | box-shadow: inset 0 -1px 2px hsla(0, 0%, 100%, .4), 0 2px 3px rgba(0, 0, 0, .4);
248 | border-width: .2rem .4rem 1.2rem;
249 | border-style: solid;
250 | border-color: #666 #222 #111 #555;
251 | height: 60%;
252 | left: 100%;
253 | position: absolute;
254 | transform: translateX(-50%);
255 | top: 0;
256 | width: 70%;
257 | z-index: 1;
258 | }
259 | ```
260 |
261 | # 播放钢琴音
262 |
263 | 当我们实现完钢琴界面,我们就需要为每个按键匹配声音,这里使用 HTML5 的 `