进阶了之后,改善了音色,改良了乐谱格式,但需要node.js支持,安装npm install soundfont-player -g
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高级钢琴模拟器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background-color: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.piano-container {
width: 100%;
max-width: 1000px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
padding: 20px;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 20px;
font-size: 28px;
}
.control-panel {
background-color: #f9f9f9;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
}
h2 {
font-size: 20px;
margin-bottom: 10px;
color: #555;
}
.control-row {
display: flex;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
gap: 10px;
}
select {
padding: 10px;
border-radius: 5px;
border: 1px solid #ddd;
background-color: white;
font-size: 14px;
flex-grow: 1;
min-width: 200px;
}
button {
background-color: #4a90e2;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
button:hover {
background-color: #357abd;
}
button.stop {
background-color: #e25c4a;
}
button.stop:hover {
background-color: #c4503e;
}
#import-button {
background-color: #4CAF50;
}
#import-button:hover {
background-color: #3e8e41;
}
.music-info {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
}
#song-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
}
#composer {
font-size: 14px;
color: #777;
}
.piano-keyboard {
display: flex;
height: 200px;
position: relative;
border-radius: 5px;
overflow: hidden;
border: 1px solid #ddd;
user-select: none;
}
.piano-key {
position: relative;
cursor: pointer;
transition: background-color 0.1s;
}
.white {
background-color: #fff;
height: 100%;
flex-grow: 1;
border-right: 1px solid #ddd;
z-index: 1;
}
.white:last-child {
border-right: none;
}
.white.active {
background-color: #f0f0f0;
}
.black {
position: absolute;
width: 60%;
height: 60%;
background-color: #333;
z-index: 2;
border-radius: 0 0 5px 5px;
}
.black.active {
background-color: #555;
}
.note-label {
position: absolute;
bottom: 10px;
width: 100%;
text-align: center;
font-size: 12px;
color: #888;
}
.loading-message {
text-align: center;
margin: 20px 0;
color: #777;
font-style: italic;
}
.visualizer {
height: 50px;
background-color: #f0f0f0;
margin: 20px 0;
border-radius: 5px;
overflow: hidden;
}
.format-info {
margin-top: 20px;
background-color: #f9f9f9;
border-radius: 8px;
padding: 15px;
}
.format-info h3 {
margin-bottom: 10px;
color: #555;
}
.format-info pre {
background-color: #f0f0f0;
padding: 10px;
border-radius: 5px;
overflow-x: auto;
font-size: 12px;
margin: 10px 0;
}
.format-info summary {
cursor: pointer;
color: #4a90e2;
margin: 10px 0;
font-weight: bold;
}
.hidden {
display: none;
}
#file-input {
display: none;
}
/* 文档样式 */
.documentation {
margin-top: 20px;
border-top: 1px solid #eee;
padding-top: 20px;
}
.documentation details {
margin-bottom: 15px;
}
.documentation summary {
font-weight: bold;
cursor: pointer;
color: #4a90e2;
margin-bottom: 10px;
}
/* 为各键分配位置 */
.key-C {
left: 0%;
}
.key-CSharp {
left: 1.785%;
}
.key-D {
left: 3.57%;
}
.key-DSharp {
left: 5.355%;
}
.key-E {
left: 7.14%;
}
.key-F {
left: 10.71%;
}
.key-FSharp {
left: 12.495%;
}
.key-G {
left: 14.28%;
}
.key-GSharp {
left: 16.065%;
}
.key-A {
left: 17.85%;
}
.key-ASharp {
left: 19.635%;
}
.key-B {
left: 21.42%;
}
</style>
</head>
<body>
<div class="piano-container">
<h1>高级钢琴模拟器</h1>
<div class="control-panel">
<h2>乐曲选择与控制</h2>
<div class="control-row">
<select id="song-select">
<option value="">-- 请选择乐曲 --</option>
<option value="moonlight_sonata">贝多芬月光奏鸣曲</option>
<option value="fur_elise">致爱丽丝</option>
</select>
<button id="play-button">播放</button>
<button id="import-button">添加乐曲</button>
<input type="file" id="file-input" accept=".json">
</div>
<div class="music-info">
<div id="song-title"></div>
<div id="composer"></div>
</div>
</div>
<div id="loading-message" class="loading-message">
正在加载高品质钢琴音色,请稍候...
</div>
<div class="visualizer" id="visualizer"></div>
<div class="piano-keyboard" id="piano-keyboard">
<!-- 钢琴键盘将通过JavaScript生成 -->
</div>
<div class="documentation">
<details>
<summary>乐曲格式说明</summary>
<div class="format-info">
<h3>乐曲JSON格式</h3>
<p>要添加自定义乐曲,请使用以下JSON格式:</p>
<pre>
{
"metadata": {
"title": "乐曲标题",
"composer": "作曲家",
"arranger": "编曲者",
"tempo": 120, // 速度(BPM)
"timeSignature": [4, 4], // 拍号
"keySignature": "C" // 调号
},
"sections": [
{
"name": "主题",
"tracks": [
{
"name": "右手旋律",
"notes": [
{ "pitch": "C4", "startTime": 0, "duration": 1, "velocity": 0.8 },
{ "pitch": "E4", "startTime": 1, "duration": 0.5, "velocity": 0.7 },
// 更多音符...
]
},
{
"name": "左手伴奏",
"notes": [
{ "pitch": "C3", "startTime": 0, "duration": 0.5, "velocity": 0.6 },
{ "pitch": "G3", "startTime": 0.5, "duration": 0.5, "velocity": 0.6 },
// 更多音符...
]
}
]
}
],
"pedals": [
{ "startTime": 0, "endTime": 4, "type": "sustain", "depth": 0.7 },
// 更多踏板控制...
]
}
</pre>
<p>格式说明:</p>
<ul>
<li><strong>metadata</strong>: 乐曲的基本信息</li>
<li><strong>sections</strong>: 乐曲的各个部分</li>
<li><strong>tracks</strong>: 每个部分中的音轨(通常是右手和左手)</li>
<li><strong>notes</strong>: 各个音符</li>
<li><strong>pitch</strong>: 音高,使用科学音调记法(如C4为中央C)</li>
<li><strong>startTime</strong>: 开始时间(以拍为单位)</li>
<li><strong>duration</strong>: 持续时间(以拍为单位)</li>
<li><strong>velocity</strong>: 力度(0-1之间的浮点数)</li>
<li><strong>pedals</strong>: 踏板控制(可选)</li>
</ul>
</div>
</details>
<details>
<summary>示例乐曲下载</summary>
<div>
<p>以下是一些示例乐曲JSON文件,您可以下载并导入到钢琴模拟器中:</p>
<ul style="margin-top: 10px; margin-bottom: 10px;">
<li><a href="#" id="canon-link">卡农 (Canon in D)</a></li>
<li><a href="#" id="clair-de-lune-link">月光 (Clair de Lune)</a></li>
<li><a href="#" id="butterfly-lovers-link">梁祝 (Butterfly Lovers)</a></li>
</ul>
<p><em>点击链接将下载JSON文件,然后您可以使用"添加乐曲"按钮导入。</em></p>
</div>
</details>
<details>
<summary>键盘快捷键</summary>
<div>
<p>您可以使用键盘演奏钢琴:</p>
<ul style="margin-top: 10px;">
<li><strong>白键</strong>: A, S, D, F, G, H, J, K</li>
<li><strong>黑键</strong>: W, E, T, Y, U</li>
<li><strong>空格键</strong>: 播放/停止当前选择的乐曲</li>
</ul>
</div>
</details>
</div>
</div>
<!-- Soundfont-Player 库 -->
<script src="https://cdn.jsdelivr.net/npm/soundfont-player@0.12.0/dist/soundfont-player.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const songSelectElement = document.getElementById('song-select');
const songTitleElement = document.getElementById('song-title');
const composerElement = document.getElementById('composer');
const keyboardElement = document.getElementById('piano-keyboard');
const loadingMessage = document.getElementById('loading-message');
const playButton = document.getElementById('play-button');
const importButton = document.getElementById('import-button');
const fileInput = document.getElementById('file-input');
const visualizer = document.getElementById('visualizer');
// 音符信息
const NOTES = [
{ note: 'C', octave: 3 },
{ note: 'C#', octave: 3 },
{ note: 'D', octave: 3 },
{ note: 'D#', octave: 3 },
{ note: 'E', octave: 3 },
{ note: 'F', octave: 3 },
{ note: 'F#', octave: 3 },
{ note: 'G', octave: 3 },
{ note: 'G#', octave: 3 },
{ note: 'A', octave: 3 },
{ note: 'A#', octave: 3 },
{ note: 'B', octave: 3 },
{ note: 'C', octave: 4 },
{ note: 'C#', octave: 4 },
{ note: 'D', octave: 4 },
{ note: 'D#', octave: 4 },
{ note: 'E', octave: 4 },
{ note: 'F', octave: 4 },
{ note: 'F#', octave: 4 },
{ note: 'G', octave: 4 },
{ note: 'G#', octave: 4 },
{ note: 'A', octave: 4 },
{ note: 'A#', octave: 4 },
{ note: 'B', octave: 4 },
{ note: 'C', octave: 5 },
{ note: 'C#', octave: 5 },
{ note: 'D', octave: 5 },
{ note: 'D#', octave: 5 },
{ note: 'E', octave: 5 },
{ note: 'F', octave: 5 },
{ note: 'F#', octave: 5 },
{ note: 'G', octave: 5 },
{ note: 'G#', octave: 5 },
{ note: 'A', octave: 5 },
{ note: 'A#', octave: 5 },
{ note: 'B', octave: 5 }
];
// 全局变量来存储所有乐曲
const songs = {};
// 钢琴音色类
class PianoSound {
constructor() {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.soundFont = null;
this.analyzer = null;
this.loadSoundFont();
this.setupAnalyzer();
}
setupAnalyzer() {
this.analyzer = this.audioContext.createAnalyser();
this.analyzer.fftSize = 2048;
this.analyzer.connect(this.audioContext.destination);
}
async loadSoundFont() {
try {
this.soundFont = await Soundfont.instrument(this.audioContext, 'acoustic_grand_piano', {
format: 'mp3',
soundfont: 'MusyngKite',
destination: this.analyzer
});
loadingMessage.textContent = '钢琴音色加载完成!';
setTimeout(() => {
loadingMessage.style.display = 'none';
}, 2000);
} catch (error) {
console.error('音色加载失败:', error);
loadingMessage.textContent = '音色加载失败,请刷新页面重试。';
loadingMessage.style.color = 'red';
}
}
playNote(note, velocity = 1.0, duration = 1.0) {
if (this.soundFont) {
const noteName = this.formatNoteName(note);
return this.soundFont.play(noteName, this.audioContext.currentTime, {
duration: duration,
gain: velocity
});
} else {
console.warn('音色尚未加载完成');
}
}
formatNoteName(note) {
// 将"C4"格式转换为"c4"格式(Soundfont要求小写)
if (typeof note === 'string') {
return note.toLowerCase();
}
return note;
}
stopAll() {
if (this.soundFont) {
this.soundFont.stop(this.audioContext.currentTime);
}
}
resume() {
if (this.audioContext.state === 'suspended') {
this.audioContext.resume();
}
}
}
// 乐谱定义 - 贝多芬《月光奏鸣曲》第一乐章开始部分
const moonlightSonata = {
metadata: {
title: "月光奏鸣曲 (第一乐章)",
composer: "Ludwig van Beethoven",
arranger: "钢琴简化版",
tempo: 60,
timeSignature: [4, 4],
keySignature: "C#"
},
sections: [
{
name: "主题",
tracks: [
{
name: "右手旋律",
notes: [
{ pitch: "G#4", startTime: 0, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 0.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 1, duration: 0.5, velocity: 0.7 },
{ pitch: "G#4", startTime: 1.5, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 2, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 2.5, duration: 0.5, velocity: 0.7 },
{ pitch: "G#4", startTime: 3, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 3.5, duration: 0.5, velocity: 0.7 },
{ pitch: "F#4", startTime: 4, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 4.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 5, duration: 0.5, velocity: 0.7 },
{ pitch: "F#4", startTime: 5.5, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 6, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 6.5, duration: 0.5, velocity: 0.7 },
{ pitch: "F#4", startTime: 7, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 7.5, duration: 0.5, velocity: 0.7 },
{ pitch: "G#4", startTime: 8, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 8.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 9, duration: 0.5, velocity: 0.7 },
{ pitch: "G#4", startTime: 9.5, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 10, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 10.5, duration: 0.5, velocity: 0.7 },
{ pitch: "G#4", startTime: 11, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 11.5, duration: 0.5, velocity: 0.7 },
{ pitch: "A4", startTime: 12, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 12.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 13, duration: 0.5, velocity: 0.7 },
{ pitch: "A4", startTime: 13.5, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 14, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 14.5, duration: 0.5, velocity: 0.7 },
{ pitch: "A4", startTime: 15, duration: 0.5, velocity: 0.7 },
{ pitch: "C#5", startTime: 15.5, duration: 0.5, velocity: 0.7 }
]
},
{
name: "左手伴奏",
notes: [
{ pitch: "C#3", startTime: 0, duration: 2, velocity: 0.6 },
{ pitch: "G#3", startTime: 2, duration: 2, velocity: 0.6 },
{ pitch: "A2", startTime: 4, duration: 2, velocity: 0.6 },
{ pitch: "F#3", startTime: 6, duration: 2, velocity: 0.6 },
{ pitch: "C#3", startTime: 8, duration: 2, velocity: 0.6 },
{ pitch: "G#3", startTime: 10, duration: 2, velocity: 0.6 },
{ pitch: "A2", startTime: 12, duration: 2, velocity: 0.6 },
{ pitch: "E3", startTime: 14, duration: 2, velocity: 0.6 }
]
}
]
}
],
pedals: [
{ startTime: 0, endTime: 4, type: "sustain", depth: 0.8 },
{ startTime: 4, endTime: 8, type: "sustain", depth: 0.8 },
{ startTime: 8, endTime: 12, type: "sustain", depth: 0.8 },
{ startTime: 12, endTime: 16, type: "sustain", depth: 0.8 }
]
};
// 乐谱定义 - 贝多芬《致爱丽丝》开始部分
const furElise = {
metadata: {
title: "致爱丽丝",
composer: "Ludwig van Beethoven",
arranger: "钢琴简化版",
tempo: 72,
timeSignature: [3, 8],
keySignature: "A"
},
sections: [
{
name: "主题",
tracks: [
{
name: "右手旋律",
notes: [
{ pitch: "E5", startTime: 0, duration: 0.5, velocity: 0.8 },
{ pitch: "D#5", startTime: 0.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 1, duration: 0.5, velocity: 0.7 },
{ pitch: "D#5", startTime: 1.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 2, duration: 0.5, velocity: 0.7 },
{ pitch: "B4", startTime: 2.5, duration: 0.5, velocity: 0.7 },
{ pitch: "D5", startTime: 3, duration: 0.5, velocity: 0.7 },
{ pitch: "C5", startTime: 3.5, duration: 0.5, velocity: 0.7 },
{ pitch: "A4", startTime: 4, duration: 1, velocity: 0.65 },
{ pitch: "C4", startTime: 5.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E4", startTime: 6, duration: 0.5, velocity: 0.7 },
{ pitch: "A4", startTime: 6.5, duration: 0.5, velocity: 0.7 },
{ pitch: "B4", startTime: 7, duration: 1, velocity: 0.7 },
{ pitch: "E4", startTime: 8.5, duration: 0.5, velocity: 0.7 },
{ pitch: "G#4", startTime: 9, duration: 0.5, velocity: 0.7 },
{ pitch: "B4", startTime: 9.5, duration: 0.5, velocity: 0.7 },
{ pitch: "C5", startTime: 10, duration: 1, velocity: 0.7 },
{ pitch: "E4", startTime: 11.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 12, duration: 0.5, velocity: 0.8 },
{ pitch: "D#5", startTime: 12.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 13, duration: 0.5, velocity: 0.7 },
{ pitch: "D#5", startTime: 13.5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 14, duration: 0.5, velocity: 0.7 },
{ pitch: "B4", startTime: 14.5, duration: 0.5, velocity: 0.7 },
{ pitch: "D5", startTime: 15, duration: 0.5, velocity: 0.7 },
{ pitch: "C5", startTime: 15.5, duration: 0.5, velocity: 0.7 },
{ pitch: "A4", startTime: 16, duration: 1, velocity: 0.65 }
]
},
{
name: "左手伴奏",
notes: [
{ pitch: "A2", startTime: 0, duration: 0.5, velocity: 0.6 },
{ pitch: "E3", startTime: 0.5, duration: 0.5, velocity: 0.6 },
{ pitch: "A3", startTime: 1, duration: 1, velocity: 0.6 },
{ pitch: "A2", startTime: 2.5, duration: 0.5, velocity: 0.6 },
{ pitch: "E3", startTime: 3, duration: 0.5, velocity: 0.6 },
{ pitch: "A3", startTime: 3.5, duration: 1, velocity: 0.6 },
{ pitch: "A2", startTime: 5, duration: 0.5, velocity: 0.6 },
{ pitch: "E3", startTime: 5.5, duration: 0.5, velocity: 0.6 },
{ pitch: "A3", startTime: 6, duration: 1, velocity: 0.6 },
{ pitch: "E2", startTime: 8, duration: 0.5, velocity: 0.6 },
{ pitch: "E3", startTime: 8.5, duration: 0.5, velocity: 0.6 },
{ pitch: "G#3", startTime: 9, duration: 1, velocity: 0.6 },
{ pitch: "A2", startTime: 11, duration: 0.5, velocity: 0.6 },
{ pitch: "E3", startTime: 11.5, duration: 0.5, velocity: 0.6 },
{ pitch: "A3", startTime: 12, duration: 1, velocity: 0.6 },
{ pitch: "A2", startTime: 14, duration: 0.5, velocity: 0.6 },
{ pitch: "E3", startTime: 14.5, duration: 0.5, velocity: 0.6 },
{ pitch: "A3", startTime: 15, duration: 1, velocity: 0.6 }
]
}
]
}
],
pedals: [
{ startTime: 0, endTime: 4, type: "sustain", depth: 0.5 },
{ startTime: 5, endTime: 8, type: "sustain", depth: 0.5 },
{ startTime: 8, endTime: 11, type: "sustain", depth: 0.5 },
{ startTime: 11, endTime: 16, type: "sustain", depth: 0.5 }
]
};
// 示例乐曲 - 卡农 D大调
const canonInD = {
metadata: {
title: "卡农 D大调",
composer: "Johann Pachelbel",
arranger: "钢琴简化版",
tempo: 70,
timeSignature: [4, 4],
keySignature: "D"
},
sections: [
{
name: "主题",
tracks: [
{
name: "右手旋律",
notes: [
{ pitch: "F#5", startTime: 0, duration: 1, velocity: 0.7 },
{ pitch: "E5", startTime: 1, duration: 1, velocity: 0.7 },
{ pitch: "D5", startTime: 2, duration: 1, velocity: 0.7 },
{ pitch: "C#5", startTime: 3, duration: 1, velocity: 0.7 },
{ pitch: "B4", startTime: 4, duration: 1, velocity: 0.7 },
{ pitch: "A4", startTime: 5, duration: 1, velocity: 0.7 },
{ pitch: "B4", startTime: 6, duration: 1, velocity: 0.7 },
{ pitch: "C#5", startTime: 7, duration: 1, velocity: 0.7 },
{ pitch: "D5", startTime: 8, duration: 1, velocity: 0.75 },
{ pitch: "C#5", startTime: 9, duration: 1, velocity: 0.7 },
{ pitch: "B4", startTime: 10, duration: 1, velocity: 0.7 },
{ pitch: "A4", startTime: 11, duration: 1, velocity: 0.7 },
{ pitch: "G4", startTime: 12, duration: 1, velocity: 0.7 },
{ pitch: "F#4", startTime: 13, duration: 1, velocity: 0.7 },
{ pitch: "G4", startTime: 14, duration: 1, velocity: 0.7 },
{ pitch: "E4", startTime: 15, duration: 1, velocity: 0.7 }
]
},
{
name: "左手伴奏",
notes: [
{ pitch: "D3", startTime: 0, duration: 2, velocity: 0.6 },
{ pitch: "A3", startTime: 2, duration: 2, velocity: 0.6 },
{ pitch: "B2", startTime: 4, duration: 2, velocity: 0.6 },
{ pitch: "F#3", startTime: 6, duration: 2, velocity: 0.6 },
{ pitch: "G2", startTime: 8, duration: 2, velocity: 0.6 },
{ pitch: "D3", startTime: 10, duration: 2, velocity: 0.6 },
{ pitch: "G2", startTime: 12, duration: 2, velocity: 0.6 },
{ pitch: "A2", startTime: 14, duration: 2, velocity: 0.6 }
]
}
]
}
],
pedals: [
{ startTime: 0, endTime: 4, type: "sustain", depth: 0.6 },
{ startTime: 4, endTime: 8, type: "sustain", depth: 0.6 },
{ startTime: 8, endTime: 12, type: "sustain", depth: 0.6 },
{ startTime: 12, endTime: 16, type: "sustain", depth: 0.6 }
]
};
// 示例乐曲 - 月光 (Clair de Lune)
const clairDeLune = {
metadata: {
title: "月光 (Clair de Lune)",
composer: "Claude Debussy",
arranger: "钢琴简化版",
tempo: 66,
timeSignature: [9, 8],
keySignature: "Db"
},
sections: [
{
name: "主题",
tracks: [
{
name: "右手旋律",
notes: [
{ pitch: "Db5", startTime: 0, duration: 1.5, velocity: 0.6 },
{ pitch: "Bb4", startTime: 1.5, duration: 0.75, velocity: 0.65 },
{ pitch: "Ab4", startTime: 2.25, duration: 0.75, velocity: 0.6 },
{ pitch: "Bb4", startTime: 3, duration: 1.5, velocity: 0.65 },
{ pitch: "Gb4", startTime: 4.5, duration: 0.75, velocity: 0.6 },
{ pitch: "F4", startTime: 5.25, duration: 0.75, velocity: 0.55 },
{ pitch: "Eb5", startTime: 6, duration: 1.5, velocity: 0.7 },
{ pitch: "Db5", startTime: 7.5, duration: 0.75, velocity: 0.65 },
{ pitch: "Bb4", startTime: 8.25, duration: 0.75, velocity: 0.6 },
{ pitch: "Ab4", startTime: 9, duration: 2.25, velocity: 0.55 },
{ pitch: "Bb4", startTime: 11.25, duration: 0.75, velocity: 0.6 },
{ pitch: "Db5", startTime: 12, duration: 1.5, velocity: 0.65 },
{ pitch: "Bb4", startTime: 13.5, duration: 0.75, velocity: 0.6 },
{ pitch: "Ab4", startTime: 14.25, duration: 0.75, velocity: 0.55 }
]
},
{
name: "左手伴奏",
notes: [
{ pitch: "Db3", startTime: 0, duration: 0.75, velocity: 0.5 },
{ pitch: "Ab3", startTime: 0.75, duration: 0.75, velocity: 0.5 },
{ pitch: "F4", startTime: 1.5, duration: 0.75, velocity: 0.5 },
{ pitch: "Eb4", startTime: 2.25, duration: 0.75, velocity: 0.5 },
{ pitch: "Eb3", startTime: 3, duration: 0.75, velocity: 0.5 },
{ pitch: "Bb3", startTime: 3.75, duration: 0.75, velocity: 0.5 },
{ pitch: "Eb4", startTime: 4.5, duration: 0.75, velocity: 0.5 },
{ pitch: "Db4", startTime: 5.25, duration: 0.75, velocity: 0.5 },
{ pitch: "Gb3", startTime: 6, duration: 0.75, velocity: 0.5 },
{ pitch: "Db4", startTime: 6.75, duration: 0.75, velocity: 0.5 },
{ pitch: "F4", startTime: 7.5, duration: 0.75, velocity: 0.5 },
{ pitch: "Eb4", startTime: 8.25, duration: 0.75, velocity: 0.5 },
{ pitch: "Db3", startTime: 9, duration: 0.75, velocity: 0.5 },
{ pitch: "Ab3", startTime: 9.75, duration: 0.75, velocity: 0.5 },
{ pitch: "F4", startTime: 10.5, duration: 0.75, velocity: 0.5 },
{ pitch: "Eb4", startTime: 11.25, duration: 0.75, velocity: 0.5 },
{ pitch: "Db3", startTime: 12, duration: 0.75, velocity: 0.5 },
{ pitch: "Ab3", startTime: 12.75, duration: 0.75, velocity: 0.5 },
{ pitch: "F4", startTime: 13.5, duration: 0.75, velocity: 0.5 },
{ pitch: "Eb4", startTime: 14.25, duration: 0.75, velocity: 0.5 }
]
}
]
}
],
pedals: [
{ startTime: 0, endTime: 3, type: "sustain", depth: 0.7 },
{ startTime: 3, endTime: 6, type: "sustain", depth: 0.7 },
{ startTime: 6, endTime: 9, type: "sustain", depth: 0.7 },
{ startTime: 9, endTime: 12, type: "sustain", depth: 0.7 },
{ startTime: 12, endTime: 15, type: "sustain", depth: 0.7 }
]
};
// 示例乐曲 - 梁祝
const butterflyLovers = {
metadata: {
title: "梁祝",
composer: "何占豪/陈钢",
arranger: "钢琴简化版",
tempo: 72,
timeSignature: [4, 4],
keySignature: "G"
},
sections: [
{
name: "主题",
tracks: [
{
name: "右手旋律",
notes: [
{ pitch: "D5", startTime: 0, duration: 1, velocity: 0.8 },
{ pitch: "E5", startTime: 1, duration: 0.5, velocity: 0.8 },
{ pitch: "G5", startTime: 1.5, duration: 0.5, velocity: 0.8 },
{ pitch: "A5", startTime: 2, duration: 1, velocity: 0.85 },
{ pitch: "G5", startTime: 3, duration: 1, velocity: 0.75 },
{ pitch: "E5", startTime: 4, duration: 1, velocity: 0.75 },
{ pitch: "D5", startTime: 5, duration: 0.5, velocity: 0.7 },
{ pitch: "E5", startTime: 5.5, duration: 0.5, velocity: 0.7 },
{ pitch: "D5", startTime: 6, duration: 1, velocity: 0.75 },
{ pitch: "B4", startTime: 7, duration: 1, velocity: 0.7 },
{ pitch: "A4", startTime: 8, duration: 1, velocity: 0.7 },
{ pitch: "B4", startTime: 9, duration: 0.5, velocity: 0.75 },
{ pitch: "D5", startTime: 9.5, duration: 0.5, velocity: 0.8 },
{ pitch: "G5", startTime: 10, duration: 1, velocity: 0.85 },
{ pitch: "E5", startTime: 11, duration: 1, velocity: 0.8 },
{ pitch: "D5", startTime: 12, duration: 1.5, velocity: 0.75 },
{ pitch: "B4", startTime: 13.5, duration: 0.5, velocity: 0.7 },
{ pitch: "A4", startTime: 14, duration: 1, velocity: 0.7 },
{ pitch: "G4", startTime: 15, duration: 1, velocity: 0.65 }
]
},
{
name: "左手伴奏",
notes: [
{ pitch: "G3", startTime: 0, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 0.5, duration: 0.5, velocity: 0.6 },
{ pitch: "D4", startTime: 1, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 1.5, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 2, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 2.5, duration: 0.5, velocity: 0.6 },
{ pitch: "D4", startTime: 3, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 3.5, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 4, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 4.5, duration: 0.5, velocity: 0.6 },
{ pitch: "D4", startTime: 5, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 5.5, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 6, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 6.5, duration: 0.5, velocity: 0.6 },
{ pitch: "D4", startTime: 7, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 7.5, duration: 0.5, velocity: 0.6 },
{ pitch: "E3", startTime: 8, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 8.5, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 9, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 9.5, duration: 0.5, velocity: 0.6 },
{ pitch: "E3", startTime: 10, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 10.5, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 11, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 11.5, duration: 0.5, velocity: 0.6 },
{ pitch: "D3", startTime: 12, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 12.5, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 13, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 13.5, duration: 0.5, velocity: 0.6 },
{ pitch: "D3", startTime: 14, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 14.5, duration: 0.5, velocity: 0.6 },
{ pitch: "B3", startTime: 15, duration: 0.5, velocity: 0.6 },
{ pitch: "G3", startTime: 15.5, duration: 0.5, velocity: 0.6 }
]
}
]
}
],
pedals: [
{ startTime: 0, endTime: 4, type: "sustain", depth: 0.7 },
{ startTime: 4, endTime: 8, type: "sustain", depth: 0.7 },
{ startTime: 8, endTime: 12, type: "sustain", depth: 0.7 },
{ startTime: 12, endTime: 16, type: "sustain", depth: 0.7 }
]
};
// 音乐播放器类
class MusicPlayer {
constructor(pianoSound) {
this.pianoSound = pianoSound;
this.isPlaying = false;
this.currentScore = null;
this.startTime = 0;
this.scheduledNotes = [];
this.tempo = 60;
this.visualizerInterval = null;
}
loadScore(musicScore) {
this.currentScore = musicScore;
this.tempo = musicScore.metadata.tempo || 60;
// 更新界面信息
songTitleElement.textContent = musicScore.metadata.title || '未知曲目';
composerElement.textContent = musicScore.metadata.composer || '未知作者';
console.log(`加载乐曲:${musicScore.metadata.title}`);
}
play() {
if (!this.currentScore) {
alert("请先选择一首乐曲");
return;
}
// 确保音频上下文已恢复
this.pianoSound.resume();
if (this.isPlaying) {
this.stop();
return;
}
this.isPlaying = true;
this.startTime = this.pianoSound.audioContext.currentTime;
// 更新播放按钮
playButton.textContent = "停止";
playButton.classList.add("stop");
// 遍历所有乐段和音轨来调度音符
this.currentScore.sections.forEach(section => {
section.tracks.forEach(track => {
track.notes.forEach(note => {
// 计算实际开始时间(考虑速度)
const beatDuration = 60 / this.tempo; // 一拍的时长(秒)
const startTimeInSeconds = this.startTime + (note.startTime * beatDuration);
const durationInSeconds = note.duration * beatDuration;
// 调度音符播放
const scheduledNote = setTimeout(() => {
if (this.isPlaying) {
const noteWithOctave = note.pitch;
const keyElement = document.querySelector(`[data-note="${noteWithOctave}"]`);
if (keyElement) {
// 为按下的键添加视觉效果
keyElement.classList.add('active');
setTimeout(() => {
keyElement.classList.remove('active');
}, durationInSeconds * 1000);
}
this.pianoSound.playNote(noteWithOctave, note.velocity, durationInSeconds);
}
}, (startTimeInSeconds - this.pianoSound.audioContext.currentTime) * 1000);
this.scheduledNotes.push(scheduledNote);
});
});
});
// 处理踏板
if (this.currentScore.pedals) {
this.currentScore.pedals.forEach(pedal => {
const beatDuration = 60 / this.tempo;
const startTimeInSeconds = this.startTime + (pedal.startTime * beatDuration);
const endTimeInSeconds = this.startTime + (pedal.endTime * beatDuration);
// 踏板开始
setTimeout(() => {
if (this.isPlaying) {
// 控制踏板开始
console.log(`踏板开始: ${pedal.type}, 深度: ${pedal.depth}`);
// 实际踏板逻辑需要与音频引擎集成
}
}, (startTimeInSeconds - this.pianoSound.audioContext.currentTime) * 1000);
// 踏板结束
setTimeout(() => {
if (this.isPlaying) {
// 控制踏板结束
console.log(`踏板结束: ${pedal.type}`);
}
}, (endTimeInSeconds - this.pianoSound.audioContext.currentTime) * 1000);
});
}
// 启动可视化
this.startVisualizer();
// 添加乐曲结束回调
if (this.currentScore.sections && this.currentScore.sections.length > 0) {
let maxEndTime = 0;
// 找出最后一个音符的结束时间
this.currentScore.sections.forEach(section => {
section.tracks.forEach(track => {
track.notes.forEach(note => {
const endTime = note.startTime + note.duration;
if (endTime > maxEndTime) {
maxEndTime = endTime;
}
});
});
});
// 设置乐曲结束的回调
const songEndTime = this.startTime + (maxEndTime * 60 / this.tempo);
const timeUntilEnd = (songEndTime - this.pianoSound.audioContext.currentTime) * 1000;
setTimeout(() => {
if (this.isPlaying) {
this.stop();
}
}, timeUntilEnd + 500); // 加500ms的余量
}
}
stop() {
this.isPlaying = false;
// 更新播放按钮
playButton.textContent = "播放";
playButton.classList.remove("stop");
// 清除所有计划播放的音符
this.scheduledNotes.forEach(noteTimeout => {
clearTimeout(noteTimeout);
});
this.scheduledNotes = [];
// 停止所有正在播放的声音
this.pianoSound.stopAll();
// 移除所有键盘上的活动状态
document.querySelectorAll('.piano-key.active').forEach(key => {
key.classList.remove('active');
});
// 停止可视化
this.stopVisualizer();
}
startVisualizer() {
// 停止之前的可视化器
this.stopVisualizer();
// 启动新的可视化器
const analyser = this.pianoSound.analyzer;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const canvas = document.createElement('canvas');
canvas.width = visualizer.offsetWidth;
canvas.height = visualizer.offsetHeight;
visualizer.innerHTML = '';
visualizer.appendChild(canvas);
const canvasCtx = canvas.getContext('2d');
const draw = () => {
if (!this.isPlaying) return;
this.visualizerAnimationFrame = requestAnimationFrame(draw);
analyser.getByteFrequencyData(dataArray);
canvasCtx.fillStyle = 'rgb(240, 240, 240)';
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
const barWidth = (canvas.width / bufferLength) * 2.5;
let barHeight;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i] / 2;
const gradient = canvasCtx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, 'rgb(73, 144, 226)');
gradient.addColorStop(1, 'rgb(110, 175, 255)');
canvasCtx.fillStyle = gradient;
canvasCtx.fillRect(x, canvas.height - barHeight / 2, barWidth, barHeight);
x += barWidth + 1;
}
};
draw();
}
stopVisualizer() {
if (this.visualizerAnimationFrame) {
cancelAnimationFrame(this.visualizerAnimationFrame);
this.visualizerAnimationFrame = null;
}
}
}
// 初始化钢琴键盘
function initPianoKeyboard() {
keyboardElement.innerHTML = '';
// 创建白键和黑键的容器
const whiteKeysContainer = document.createElement('div');
whiteKeysContainer.className = 'white-keys';
whiteKeysContainer.style.display = 'flex';
whiteKeysContainer.style.width = '100%';
whiteKeysContainer.style.height = '100%';
whiteKeysContainer.style.position = 'relative';
// 计算白键数量
const whiteNotes = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
let whiteKeyCount = 0;
NOTES.forEach(noteInfo => {
if (!noteInfo.note.includes('#')) {
whiteKeyCount++;
}
});
// 创建白键
NOTES.forEach(noteInfo => {
const note = noteInfo.note;
const octave = noteInfo.octave;
if (!note.includes('#')) { // 白键
const keyElement = document.createElement('div');
keyElement.className = 'piano-key white';
keyElement.dataset.note = `${note}${octave}`;
// 添加音符标签
const noteLabel = document.createElement('div');
noteLabel.className = 'note-label';
noteLabel.textContent = `${note}${octave}`;
keyElement.appendChild(noteLabel);
whiteKeysContainer.appendChild(keyElement);
}
});
keyboardElement.appendChild(whiteKeysContainer);
// 现在添加黑键,正确定位它们
const whiteKeyWidth = 100 / whiteKeyCount;
let whiteKeyIndex = 0;
NOTES.forEach(noteInfo => {
const note = noteInfo.note;
const octave = noteInfo.octave;
if (!note.includes('#')) {
whiteKeyIndex++;
} else { // 黑键
const keyElement = document.createElement('div');
keyElement.className = 'piano-key black';
keyElement.dataset.note = `${note}${octave}`;
// 计算黑键位置
// 根据前一个白键的位置,将黑键定位在白键之间
const position = (whiteKeyIndex - 0.5) * whiteKeyWidth;
keyElement.style.left = `${position}%`;
keyElement.style.width = `${whiteKeyWidth * 0.7}%`;
keyboardElement.appendChild(keyElement);
}
});
// 添加钢琴键点击和触摸事件
const pianoKeys = document.querySelectorAll('.piano-key');
const pianoSound = new PianoSound();
pianoKeys.forEach(key => {
const note = key.dataset.note;
// 鼠标事件
key.addEventListener('mousedown', () => {
pianoSound.resume();
pianoSound.playNote(note, 0.8, 0.5);
key.classList.add('active');
});
key.addEventListener('mouseup', () => {
key.classList.remove('active');
});
key.addEventListener('mouseleave', () => {
key.classList.remove('active');
});
// 触摸事件
key.addEventListener('touchstart', (e) => {
e.preventDefault();
pianoSound.resume();
pianoSound.playNote(note, 0.8, 0.5);
key.classList.add('active');
});
key.addEventListener('touchend', (e) => {
e.preventDefault();
key.classList.remove('active');
});
});
return pianoSound;
}
// 初始化示例乐曲
function initSongs() {
songs['moonlight_sonata'] = moonlightSonata;
songs['fur_elise'] = furElise;
songs['canon_in_d'] = canonInD;
songs['clair_de_lune'] = clairDeLune;
songs['butterfly_lovers'] = butterflyLovers;
}
// 下载示例JSON文件
function generateJsonDownload(songId, songData) {
const element = document.getElementById(songId);
if (!element) return;
element.addEventListener('click', (e) => {
e.preventDefault();
const jsonString = JSON.stringify(songData, null, 2);
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${songData.metadata.title.replace(/\s+/g, '_')}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
}
// 主程序
const pianoSound = initPianoKeyboard();
const musicPlayer = new MusicPlayer(pianoSound);
// 初始化所有示例乐曲
initSongs();
// 生成示例JSON下载
generateJsonDownload('canon-link', canonInD);
generateJsonDownload('clair-de-lune-link', clairDeLune);
generateJsonDownload('butterfly-lovers-link', butterflyLovers);
// 绑定歌曲选择事件
songSelectElement.addEventListener('change', () => {
const selectedSongId = songSelectElement.value;
if (selectedSongId && songs[selectedSongId]) {
musicPlayer.loadScore(songs[selectedSongId]);
} else {
songTitleElement.textContent = '';
composerElement.textContent = '';
musicPlayer.currentScore = null;
}
});
// 绑定播放/停止按钮事件
playButton.addEventListener('click', () => {
if (musicPlayer.isPlaying) {
musicPlayer.stop();
} else {
musicPlayer.play();
}
});
// 导入自定义乐曲
importButton.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const jsonData = JSON.parse(event.target.result);
// 简单验证JSON格式
if (!jsonData.metadata || !jsonData.sections) {
throw new Error('乐曲格式不正确');
}
// 生成唯一ID
const songId = 'custom_' + Date.now();
songs[songId] = jsonData;
// 添加到下拉菜单
const option = document.createElement('option');
option.value = songId;
option.textContent = jsonData.metadata.title || `自定义乐曲 ${Object.keys(songs).length}`;
songSelectElement.appendChild(option);
// 选择并加载这首歌
songSelectElement.value = songId;
musicPlayer.loadScore(jsonData);
alert(`已成功导入乐曲: ${jsonData.metadata.title || '未命名乐曲'}`);
} catch (error) {
alert(`导入失败: ${error.message}`);
console.error('导入错误:', error);
}
};
reader.readAsText(file);
// 重置文件输入,以便可以重新导入同一文件
fileInput.value = '';
});
// 处理键盘事件
document.addEventListener('keydown', (event) => {
// 防止重复触发
if (event.repeat) return;
// 空格键控制播放/停止
if (event.code === 'Space') {
event.preventDefault();
if (musicPlayer.isPlaying) {
musicPlayer.stop();
} else {
musicPlayer.play();
}
return;
}
const keyMap = {
'a': 'C4', 's': 'D4', 'd': 'E4', 'f': 'F4', 'g': 'G4', 'h': 'A4', 'j': 'B4', 'k': 'C5',
'w': 'C#4', 'e': 'D#4', 't': 'F#4', 'y': 'G#4', 'u': 'A#4'
};
const note = keyMap[event.key.toLowerCase()];
if (note) {
const keyElement = document.querySelector(`[data-note="${note}"]`);
if (keyElement) {
pianoSound.resume();
pianoSound.playNote(note, 0.8, 0.5);
keyElement.classList.add('active');
}
}
});
document.addEventListener('keyup', (event) => {
const keyMap = {
'a': 'C4', 's': 'D4', 'd': 'E4', 'f': 'F4', 'g': 'G4', 'h': 'A4', 'j': 'B4', 'k': 'C5',
'w': 'C#4', 'e': 'D#4', 't': 'F#4', 'y': 'G#4', 'u': 'A#4'
};
const note = keyMap[event.key.toLowerCase()];
if (note) {
const keyElement = document.querySelector(`[data-note="${note}"]`);
if (keyElement) {
keyElement.classList.remove('active');
}
}
});
// 添加窗口大小调整事件,使可视化器适应新的大小
window.addEventListener('resize', () => {
if (musicPlayer.isPlaying) {
musicPlayer.startVisualizer();
}
});
});
</script>
</body>
</html>