钢琴模拟器

简介: 这段内容介绍了一个基于网页的互动钢琴模拟器项目,包括其HTML结构、CSS样式和JavaScript功能。用户可以通过键盘或鼠标与虚拟钢琴进行交互,同时具备多种功能,如选择不同的乐器声音、录制演奏、播放录制内容、调整音调以及触发和弦。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/32da71eb50a84ccc83924d53882fe99c.gif#pic_center)

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f9a15aa2405d4dfc9cf598678139ffed.png#pic_center)

@[TOC]


## 钢琴模拟器


#### 代码结构

###### HTML结构

\<html>: HTML文档的根元素。

\<head>: 包含文档的元数据。

\<base>: 指定相对URL的基准。

\<title>: 指定页面的标题。

\<style>: 包含嵌入的CSS样式。

\<body>: 包含文档的内容。

\<div class="container">: 容器元素,包含主要内容。

\<div class="controls">: 控件区域,包含选择框、按钮和移调控制。

\<select id="instrument-select">: 乐器选择框。

\<button id="record">: 录音按钮。

\<button id="play">: 播放按钮。

\<button id="stop">: 停止按钮。

\<div class="transpose-controls">: 移调控制区域。

\<button id="transpose-down">: 移调降低按钮。

\<input type="text" id="transpose-value" readonly>: 显示当前移调值。

\<button id="transpose-up">: 移调升高按钮。

\<div id="current-instrument">: 当前乐器显示区域。

\<div id="keyboard">: 键盘区域。

\<div id="chord-pads">: 和弦按钮区域。

\<div id="loading">: 加载提示。

###### CSS样式

body, html: 设置页面的基本样式。

.container: 设置容器的样式。

.controls: 设置控件区域的样式。

select, button, input: 设置选择框、按钮和输入框的样式。

#instrument-select: 设置乐器选择框的样式。

#current-instrument, #transpose-value: 设置当前乐器和移调值的样式。

.transpose-controls: 设置移调控制区域的样式。

#keyboard: 设置键盘区域的样式。

.key: 设置键的样式。

.key.black: 设置黑键的样式。

#loading: 设置加载提示的样式。

#chord-pads: 设置和弦按钮区域的样式。

.chord-pad: 设置和弦按钮的样式。

###### JavaScript功能

**时钟更新**:

updateClock: 更新时钟的时间和日期。

setInterval(updateClock, 1000): 每秒更新一次时钟。

updateClock(): 初次加载时立即更新时钟。

**点击事件**:

监听乐器选择框、录音按钮、播放按钮和停止按钮的点击事件。

根据点击的元素执行相应的操作(如录音、播放、停止等)。

**键盘功能**:

createKeyboard: 创建键盘。

createChordPads: 创建和弦按钮。

loadSoundFonts: 加载SoundFont。

loadSoundFont: 加载指定的SoundFont。

transposeNote: 移调音符。

playNote: 播放音符。

releaseNote: 释放音符。

playChord: 播放和弦。

releaseChord: 释放和弦。

startRecording: 开始录音。

stopRecording: 停止录音。

playRecording: 播放录音。

stopPlayback: 停止播放。

updateTransposeDisplay: 更新移调显示。

#### 源码

```html

<html><head><base href="https://websim.ai/app/soundfont-keyboard"/><title>SoundFont Keyboard: Interactive Musical Experience with Chords</title>

<style>

 body {

   margin: 0;

   padding: 0;

   overflow: hidden;

   font-family: 'Arial', sans-serif;

   background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);

   color: #fff;

   display: flex;

   flex-direction: column;

   justify-content: center;

   align-items: center;

   min-height: 100vh;

 }

 .container {

   background: rgba(255, 255, 255, 0.1);

   border-radius: 20px;

   padding: 30px;

   box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);

   backdrop-filter: blur(4px);

   border: 1px solid rgba(255, 255, 255, 0.18);

   display: flex;

   flex-direction: column;

   align-items: center;

   margin-bottom: 20px;

 }

 .controls {

   display: flex;

   flex-wrap: wrap;

   justify-content: center;

   margin-bottom: 20px;

 }

 select, button, input {

   margin: 5px;

   padding: 10px 15px;

   font-size: 14px;

   background-color: rgba(255, 255, 255, 0.2);

   color: #fff;

   border: none;

   border-radius: 5px;

   cursor: pointer;

   transition: background-color 0.3s ease;

 }

 select:hover, button:hover {

   background-color: rgba(255, 255, 255, 0.3);

 }

 #instrument-select {

   width: 200px;

   appearance: none;

   -webkit-appearance: none;

   -moz-appearance: none;

   background-image: url('data:image/svg+xml;utf8,<svg fill="%23ffffff" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>');

   background-repeat: no-repeat;

   background-position-x: 95%;

   background-position-y: 50%;

 }

 #instrument-select option {

   background-color: #2a2a2a;

   color: #fff;

 }

 #current-instrument, #transpose-value {

   margin-top: 10px;

   font-style: italic;

 }

 .transpose-controls {

   display: flex;

   align-items: center;

   margin-top: 10px;

 }

 .transpose-controls button {

   width: 30px;

   height: 30px;

   padding: 0;

   font-size: 18px;

   line-height: 1;

 }

 #transpose-value {

   margin: 0 10px;

   width: 40px;

   text-align: center;

   background-color: rgba(255, 255, 255, 0.1);

 }

 #keyboard {

   display: flex;

   justify-content: center;

   background: linear-gradient(to bottom, #4a4a4a, #2a2a2a);

   padding: 20px;

   border-radius: 10px;

   box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);

 }

 .key {

   width: 40px;

   height: 150px;

   background-color: #f0f0f0;

   border: 1px solid #000;

   margin: 0 2px;

   cursor: pointer;

   border-radius: 0 0 5px 5px;

   box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);

   transition: background-color 0.1s ease;

 }

 .key.black {

   width: 30px;

   height: 100px;

   background-color: #000;

   margin-left: -15px;

   margin-right: -15px;

   z-index: 1;

 }

 .key:active, .key.active {

   background-color: #ddd;

 }

 .key.black:active, .key.black.active {

   background-color: #333;

 }

 #loading {

   position: fixed;

   top: 50%;

   left: 50%;

   transform: translate(-50%, -50%);

   font-size: 24px;

   background: rgba(0, 0, 0, 0.7);

   padding: 20px;

   border-radius: 10px;

   z-index: 20;

 }

 #chord-pads {

   display: flex;

   flex-wrap: wrap;

   justify-content: center;

   max-width: 600px;

   margin-top: 20px;

 }

 .chord-pad {

   width: 60px;

   height: 60px;

   margin: 5px;

   font-size: 16px;

   font-weight: bold;

   background-color: rgba(255, 255, 255, 0.2);

   border: none;

   border-radius: 50%;

   cursor: pointer;

   transition: background-color 0.3s ease, transform 0.1s ease;

 }

 .chord-pad:hover {

   background-color: rgba(255, 255, 255, 0.3);

 }

 .chord-pad:active {

   transform: scale(0.95);

 }

</style>

</head>

<body>

 <div class="container">

   <div class="controls">

     <select id="instrument-select">

       <option value="">Select an instrument...</option>

     </select>

     <button id="record">Record</button>

     <button id="play">Play</button>

     <button id="stop">Stop</button>

     <div class="transpose-controls">

       <button id="transpose-down">-</button>

       <input type="text" id="transpose-value" value="0" readonly>

       <button id="transpose-up">+</button>

     </div>

   </div>

   <div id="current-instrument"></div>

   <div id="keyboard"></div>

 </div>

 <div id="chord-pads"></div>

 <div id="loading">Loading SoundFonts...</div>


 <script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.3/howler.min.js"></script>


 <script>

   const keys = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];

   const keyMapping = {

     'z': 'C3', 's': 'C#3', 'x': 'D3', 'd': 'D#3', 'c': 'E3', 'v': 'F3', 'g': 'F#3',

     'b': 'G3', 'h': 'G#3', 'n': 'A3', 'j': 'A#3', 'm': 'B3',

     'q': 'C4', '2': 'C#4', 'w': 'D4', '3': 'D#4', 'e': 'E4', 'r': 'F4', '5': 'F#4',

     't': 'G4', '6': 'G#4', 'y': 'A4', '7': 'A#4', 'u': 'B4',

     'i': 'C5', '9': 'C#5', 'o': 'D5', '0': 'D#5', 'p': 'E5', '[': 'F5', '=': 'F#5',

     ']': 'G5'

   };

   const octaves = 3;

   let currentInstrument = 'acoustic_grand_piano';

   let soundFont;

   let recording = false;

   let recordedNotes = [];

   let startTime;

   let playbackTimeouts = [];

   let transposeValue = 0;


   const pressedKeys = new Set();


   const chords = {

     'E': ['E4', 'G#4', 'B4'],

     'A': ['A3', 'C#4', 'E4'],

     'F': ['F3', 'A3', 'C4'],

     'D': ['D4', 'F#4', 'A4'],

     'G': ['G3', 'B3', 'D4'],

     'C': ['C4', 'E4', 'G4'],

     'B': ['B3', 'D#4', 'F#4'],

     'Em': ['E4', 'G4', 'B4'],

     'Am': ['A3', 'C4', 'E4'],

     'Dm': ['D4', 'F4', 'A4'],

     'Bm': ['B3', 'D4', 'F#4'],

     'Cm': ['C4', 'D#4', 'G4'],

     'Fm': ['F3', 'G#3', 'C4'],

     'E7': ['E4', 'G#4', 'B4', 'D5'],

     'A7': ['A3', 'C#4', 'E4', 'G4'],

     'D7': ['D4', 'F#4', 'A4', 'C5'],

     'G7': ['G3', 'B3', 'D4', 'F4'],

     'C7': ['C4', 'E4', 'G4', 'A#4']

   };


   function createKeyboard() {

     const keyboard = document.getElementById('keyboard');

     for (let octave = 3; octave < 3 + octaves; octave++) {

       keys.forEach((note) => {

         const key = document.createElement('div');

         key.className = `key ${note.includes('#') ? 'black' : 'white'}`;

         key.dataset.note = `${note}${octave}`;

         key.addEventListener('mousedown', () => playNote(`${note}${octave}`));

         key.addEventListener('mouseup', () => releaseNote(`${note}${octave}`));

         key.addEventListener('mouseleave', () => releaseNote(`${note}${octave}`));

         keyboard.appendChild(key);

       });

     }

   }


   function createChordPads() {

     const chordPads = document.getElementById('chord-pads');

     Object.keys(chords).forEach(chordName => {

       const pad = document.createElement('button');

       pad.className = 'chord-pad';

       pad.textContent = chordName;

       pad.addEventListener('mousedown', () => playChord(chordName));

       pad.addEventListener('mouseup', () => releaseChord(chordName));

       pad.addEventListener('mouseleave', () => releaseChord(chordName));

       chordPads.appendChild(pad);

     });

   }


   createKeyboard();

   createChordPads();


   async function loadSoundFonts() {

     const response = await fetch('https://gleitz.github.io/midi-js-soundfonts/MusyngKite/names.json');

     const instruments = await response.json();

     const select = document.getElementById('instrument-select');

     instruments.forEach(instrument => {

       const option = document.createElement('option');

       option.value = instrument;

       option.textContent = instrument.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());

       select.appendChild(option);

     });

     loadSoundFont('acoustic_grand_piano');

   }


   async function loadSoundFont(instrument) {

     document.getElementById('loading').style.display = 'block';

     currentInstrument = instrument;

     const response = await fetch(`https://gleitz.github.io/midi-js-soundfonts/MusyngKite/${instrument}-mp3.js`);

     const soundFontData = await response.text();

     eval(soundFontData);

     soundFont = MIDI.Soundfont[instrument];

     document.getElementById('loading').style.display = 'none';

     document.getElementById('current-instrument').textContent = `Current Instrument: ${instrument.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}`;

   }


   function transposeNote(note) {

     const [noteName, octave] = [note.slice(0, -1), parseInt(note.slice(-1))];

     let noteIndex = keys.indexOf(noteName);

     noteIndex += transposeValue;


     let newOctave = octave + Math.floor(noteIndex / 12);

     noteIndex = (noteIndex + 12) % 12; // Ensure positive index


     return `${keys[noteIndex]}${newOctave}`;

   }


   function playNote(note) {

     if (!soundFont) return;

   

     const transposedNote = transposeNote(note);

     let sound = new Howl({

       src: [soundFont[transposedNote]],

       format: ['mp3']

     });

   

     sound.play();

   

     if (recording) {

       const time = Date.now() - startTime;

       recordedNotes.push({ note, time });

     }


     // Highlight the key

     const key = document.querySelector(`.key[data-note="${note}"]`);

     if (key) {

       key.classList.add('active');

     }

   }


   function releaseNote(note) {

     // Remove highlight from the key

     const key = document.querySelector(`.key[data-note="${note}"]`);

     if (key) {

       key.classList.remove('active');

     }

   }


   function playChord(chordName) {

     if (!soundFont) return;

   

     chords[chordName].forEach(note => {

       playNote(note);

     });


     if (recording) {

       const time = Date.now() - startTime;

       recordedNotes.push({ chord: chordName, time });

     }

   }


   function releaseChord(chordName) {

     chords[chordName].forEach(note => {

       releaseNote(note);

     });

   }


   function startRecording() {

     recording = true;

     recordedNotes = [];

     startTime = Date.now();

     document.getElementById('record').textContent = 'Stop Recording';

   }


   function stopRecording() {

     recording = false;

     document.getElementById('record').textContent = 'Record';

   }


   function playRecording() {

     if (recordedNotes.length === 0) return;

     stopPlayback(); // Stop any ongoing playback

     const playbackStartTime = Date.now();

     recordedNotes.forEach(({ note, chord, time }) => {

       const timeout = setTimeout(() => {

         if (note) {

           playNote(note);

           setTimeout(() => releaseNote(note), 200);

         } else if (chord) {

           playChord(chord);

           setTimeout(() => releaseChord(chord), 200);

         }

       }, time);

       playbackTimeouts.push(timeout);

     });

   }


   function stopPlayback() {

     // Clear all scheduled playback timeouts

     playbackTimeouts.forEach(timeout => clearTimeout(timeout));

     playbackTimeouts = [];

""

   

     // Stop all currently playing sounds

     Howler.stop();

   

     // Reset all key colors

     document.querySelectorAll('.key').forEach(key => {

       key.classList.remove('active');

     });

   }


   function updateTransposeDisplay() {

     const transposeInput = document.getElementById('transpose-value');

     transposeInput.value = transposeValue >= 0 ? `+${transposeValue}` : transposeValue;

   }


   loadSoundFonts();


   document.getElementById('instrument-select').addEventListener('change', (e) => loadSoundFont(e.target.value));

   document.getElementById('record').addEventListener('click', () => {

     if (recording) {

       stopRecording();

     } else {

       startRecording();

     }

   });

   document.getElementById('play').addEventListener('click', playRecording);

   document.getElementById('stop').addEventListener('click', stopPlayback);


   document.getElementById('transpose-down').addEventListener('click', () => {

     transposeValue = Math.max(transposeValue - 1, -12);

     updateTransposeDisplay();

   });


   document.getElementById('transpose-up').addEventListener('click', () => {

     transposeValue = Math.min(transposeValue + 1, 12);

     updateTransposeDisplay();

   });


   window.addEventListener('keydown', (e) => {

     const note = keyMapping[e.key.toLowerCase()];

     if (note && !pressedKeys.has(note)) {

       pressedKeys.add(note);

       playNote(note);

     }

   });


   window.addEventListener('keyup', (e) => {

     const note = keyMapping[e.key.toLowerCase()];

     if (note) {

       pressedKeys.delete(note);

       releaseNote(note);

     }

   });


   // Initialize transpose display

   updateTransposeDisplay();

 </script>

</body></html>

```


#### 效果图

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9b767c64364244b5b550d3efcc38423c.png)



![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6756ea126fc24e81b41d019ef8cac09c.png#pic_center)

目录
相关文章
|
2月前
|
Android开发
HBuilder X连接雷电模拟器(手机模拟器)
HBuilder X连接雷电模拟器(手机模拟器)
HBuilder X连接雷电模拟器(手机模拟器)
|
29天前
|
C++
简易投影仪的制作(下)
简易投影仪的制作(下)
30 0
|
29天前
简易投影仪的制作(上)
简易投影仪的制作(上)
39 0
|
6月前
好玩的仿真过节烟花模拟器程序
好玩的仿真过节烟花模拟器程序
31 2
好玩的仿真过节烟花模拟器程序
|
6月前
雷电模拟器复制粘贴
雷电模拟器复制粘贴
581 0
|
6月前
|
Java 应用服务中间件 定位技术
雷电飞机大战游戏|基于Java开发实现雷电飞机大战游戏
雷电飞机大战游戏|基于Java开发实现雷电飞机大战游戏
|
数据安全/隐私保护 Windows
只知道camtasia?其实还有更多录屏软件可选择!
为了凑齐三张封面图,我特意制作了视频来试试。
120 0
|
Android开发 数据安全/隐私保护 iOS开发
airserver安卓版手机屏幕投屏电脑神器
AirServer一款投屏神器,可以帮你轻松地将iPhone、iPad投屏到Mac。是不是经常看到游戏主播用AirServer投屏?此外,AirServer也是视频Up主必备工具之一!用来录制演示教程不错。除了实现单个手机投屏到电脑或荧幕。如果你有多画面投屏或者跨设备投屏的需求,不妨试试这个软件。
697 0
|
前端开发
做一个好看的红白机模拟器
做一个好看的红白机模拟器
做一个好看的红白机模拟器
|
机器人 人机交互 图形学
DIY一只“眼睛”摄像头看自己工作,能眨眼睛皱眉头,还能“撸”
DIY一只“眼睛”摄像头看自己工作,能眨眼睛皱眉头,还能“撸”
211 0