在线虚拟钢琴
完整 49 键钢琴 · 键盘 / 鼠标弹奏 · 5 种音色 · 录制 / 导出
在线弹奏/录制/导出 MIDI
完整 49 键钢琴 · 键盘 / 鼠标弹奏 · 5 种音色 · 录制 / 导出
音色实现:本工具用 Web Audio API 合成音色(不依赖采样库)。钢琴音 = 多个谐波叠加 + ADSR 包络;电风琴 = 方波;弦乐 = 锯齿波 + 低通;钟 = 正弦 + 长衰减。
键盘弹奏:电脑键盘对应钢琴键,按下发声,松开停止。延音模式开后即使松开也持续到声音自然衰减。
录制:点击开始录制后所有弹奏被记录为 WebM 音频,可下载保存。
了解工具定位 · 使用场景 · 对比优势
作曲人或编曲者在通勤、散步时脑中突然冒出旋律片段,传统方法是哼唱录音或手写简谱,但转成 MIDI 很麻烦。本工具直接在浏览器里用鼠标或键盘弹奏,即时录制为 MIDI 文件,回家直接导入 DAW(数字音频工作站)继续编曲,不丢失任何一个音符的时值和力度细节。
音乐专业学生或业余爱好者需要练习音高辨别和节奏模仿,但找陪练成本高、时间不灵活。本工具提供标准音高参考,用户弹奏后能回放对比自己的音准偏差;录制功能可反复听自己的演奏,配合节拍器功能(如工具内置)进行节奏校准,实现低成本自主训练。
独立游戏开发者需要快速验证某个场景的音效或背景音乐片段,但外包作曲周期长、沟通成本高。本工具无需安装任何软件,打开网页就能用鼠标画出简单的旋律和和弦进行,导出 MIDI 后直接挂载到游戏引擎中试听,判断氛围是否匹配,大幅缩短音效原型迭代周期。
非专业歌手在去 KTV 前想熟悉某首歌的前奏、间奏或转调段落,但原曲伴奏带的人声干扰大、找不到纯伴奏版。本工具允许用户手动弹奏关键乐句,反复录制并调整速度,直到完全掌握节奏和音高变化,避免在 KTV 现场出现进错拍或跑调的尴尬。
钢琴调律师或电子琴维修师傅在完成维修后,需要快速检测所有琴键的力度响应是否一致、有无哑音或连击。本工具提供全键位弹奏录制功能,通过回放波形或 MIDI 事件列表,可以逐键检查力度值、时值是否在正常范围,比凭耳朵听更精确、可量化。
| 维度 | 本工具 | 竞品 A: Online Sequencer | 传统方法 |
|---|---|---|---|
| 数据隐私 | 纯浏览器处理,零上传 | 作品默认公开,需手动设为私密 | 完全离线,无网络传输 |
| 处理速度 | 实时弹奏,即时响应 | 页面加载需数秒,音符编辑有轻微延迟 | 即时,取决于乐器响应 |
| 离线可用 | 支持,加载后断开网络仍可弹奏 | 不支持,必须保持在线 | 完全离线 |
| 导出格式 | 标准 MIDI 文件 | MIDI + MP3 音频(需登录) | 无导出功能,需外接录音设备 |
| 收费模式 | 完全免费 | 基础免费,导出音频等功能需付费 | 需购买实体乐器 |
| 注册要求 | 无需注册,打开即用 | 需注册账号才能保存作品 | 无需注册 |
| 音色库 | 使用 Web MIDI API 默认音色 | 内置多种合成器音色 | 取决于乐器本身音色 |
| 协作功能 | 单人弹奏录制 | 支持多人协作编辑 | 需多人同室合奏 |
上手步骤 · 输入输出 · 避坑提示
| 输入 | 输出 | 说明 |
|---|---|---|
| 在键盘上依次按下 C4、E4、G4 | MIDI 音符:C4 (60)、E4 (64)、G4 (67) | 时长:各 0.5 秒 | 力度:100 | 典型场景:弹奏 C 大三和弦,验证音高对应 |
| 录制一段 10 秒的即兴旋律 | MIDI 文件:melody_20250327_1430.mid | 音符数:32 | 总时长:10.2 秒 | 常见用法:录制后导出 MIDI 用于编曲 |
| 按下中央 C (C4) 持续 3 秒后松开 | MIDI 音符:C4 (60) | 起始:0.0s | 结束:3.0s | 力度:80 | 边界 case:长按音符的起止时间精确记录 |
| 同时按下 10 个相邻白键(C4 到 E5) | MIDI 事件:10 个 Note On (60-76) | 时间戳:0.0s | 复音数:10 | 边界 case:测试复音数上限(常见工具支持 16-32 复音) |
| 以极轻力度(力度值 1)按下 A4 | MIDI 音符:A4 (69) | 力度:1 | 音量:极弱 (ppp) | 边界 case:力度值 1-127 范围的最低端 |
| 在 MIDI 键盘上弹奏黑键 C#4 | MIDI 音符:C#4 (61) | 半音偏移:+1 | 易错 case:黑键对应升号,音高编号为 61 而非 60 |
| 导出时选择“单轨道”格式 | MIDI 文件:单轨道 | 格式 0 | 所有音符合并至 Track 0 | 易错 case:单轨道 vs 多轨道格式的导出区别 |
鼠标在钢琴键上快速滑动,同时按下多个相邻键每次只点击一个键,松开后再点击下一个键;或使用键盘映射模式(如 ZXCV 对应白键)鼠标点击会触发 mousedown 事件,滑动时连续触发多个键的按下,产生非预期和弦或连音,影响录制音轨的纯净度
直接点击「录制」按钮开始弹奏,未检查音轨列表是否为空录制前先点击「清空音轨」或「新建工程」按钮,确保音轨列表为空工具默认采用叠加录制模式,新录制内容会追加到现有音轨末尾,导致导出 MIDI 包含多个重叠段落,无法分离
导出前未勾选任何音轨,直接点击「导出 MIDI」在音轨列表中勾选需要导出的音轨(至少勾选一个),再点击导出导出逻辑只处理被勾选的音轨;未勾选时生成空 MIDI 文件(0 个音符),播放器无法识别
连接蓝牙耳机后弹奏,抱怨「按下键后声音延迟半秒」使用有线耳机或设备内置扬声器弹奏;若必须用蓝牙,在系统音频设置中关闭「低延迟模式」或使用 aptX LL 编解码器浏览器 Web Audio API 的音频输出延迟通常在 10-50ms,蓝牙音频链路额外增加 100-300ms 延迟,导致明显的按键-声音不同步
双击导出的 .mid 文件,系统提示「无法播放此文件」将 .mid 文件导入 DAW(如 FL Studio、GarageBand)或 MIDI 播放器(如 TiMidity++、VLC),加载音色库后播放MIDI 文件只存储音符事件(音高、力度、时长),不包含音频波形;必须由软件合成器实时渲染成声音
在手机/平板上打开工具,点击键盘图标后尝试按物理键盘(如外接蓝牙键盘),无响应在移动端浏览器中,先点击界面上的「键盘模式」按钮(通常显示为键盘图标),确保焦点在画布上;外接键盘需确认系统已识别移动端浏览器默认不捕获物理键盘事件,必须显式将焦点设置到可键盘输入的元素(如 canvas 或 div)才能触发 keydown 事件
录制时踩下延音踏板,导出 MIDI 后在 DAW 中播放,音符没有延音效果录制前检查工具是否支持踏板事件(通常在设置中有「启用踏板」开关);若不支持,录制后手动在 DAW 中添加 CC64 控制器事件该工具的 MIDI 录制默认只捕获 Note On/Off 事件,不捕获控制器事件(CC);踏板属于 CC64,需要单独启用或后期编辑
导出 MIDI 文件,导入 DAW 后所有音符力度均为 100(或默认值)弹奏时注意按键力度(轻按 vs 重按);若使用鼠标点击,力度固定为 100;建议使用支持力度感应的 MIDI 键盘连接电脑后弹奏鼠标点击事件不包含力度信息,工具默认使用固定力度值(100);只有通过 Web MIDI API 连接的物理 MIDI 键盘才能传递真实力度
公式推导 · 流程图解 · 依据出处
f = 440 × 2^{(n - 69) / 12}
f — 音符频率(Hz)n — MIDI 音符编号(0-127)中央 C(MIDI 编号 60)的频率计算:f = 440 × 2^{(60 - 69) / 12} = 440 × 2^{-9/12} = 440 × 2^{-0.75} ≈ 440 × 0.5946 ≈ 261.6 Hz。这是钢琴键盘上第 40 个白键(C4)的标准音高。
适用于标准十二平均律调音的钢琴/电子琴,MIDI 协议定义(MMA/AMEI 标准)。不适用于非平均律乐器(如纯律古钢琴)或调音偏离 A4=440Hz 的乐器。
3 种主流语言 · 复制即用
// 使用 Web MIDI API 监听键盘输入并生成 MIDI 消息
// 需要用户交互(点击页面)后激活,且浏览器支持 Web MIDI API
async function initMIDI() {
if (!navigator.requestMIDIAccess) {
console.error('浏览器不支持 Web MIDI API');
return;
}
try {
const midiAccess = await navigator.requestMIDIAccess();
const output = midiAccess.outputs.values().next().value;
if (!output) {
console.error('未找到 MIDI 输出设备');
return;
}
// 监听键盘按键
document.addEventListener('keydown', (e) => {
// 仅处理 A-Z 键映射到 MIDI 音符(C4 开始)
const noteMap = { 'a': 60, 's': 62, 'd': 64, 'f': 65, 'g': 67, 'h': 69, 'j': 71 };
const note = noteMap[e.key.toLowerCase()];
if (note) {
// 发送 Note On 消息(0x90 = 通道 1 的 Note On)
const msg = [0x90, note, 100]; // 力度 100
output.send(msg);
console.log(`按下音符: ${note}`);
}
});
document.addEventListener('keyup', (e) => {
const noteMap = { 'a': 60, 's': 62, 'd': 64, 'f': 65, 'g': 67, 'h': 69, 'j': 71 };
const note = noteMap[e.key.toLowerCase()];
if (note) {
// 发送 Note Off 消息(0x80 = 通道 1 的 Note Off)
const msg = [0x80, note, 0];
output.send(msg);
}
});
} catch (err) {
console.error('MIDI 初始化失败:', err);
}
}
// 调用:initMIDI();import mido
import time
# 创建一个 MIDI 文件并写入音符
# 需要安装 mido 库:pip install mido
def create_midi_file(filename, notes, tempo=120):
"""
生成一个简单的 MIDI 文件
notes: 列表,每个元素为 (note_number, duration_seconds)
"""
mid = mido.MidiFile()
track = mido.MidiTrack()
mid.tracks.append(track)
# 设置速度(微秒每四分音符)
microseconds_per_beat = int(60_000_000 / tempo)
track.append(mido.MetaMessage('set_tempo', tempo=microseconds_per_beat))
for note, duration in notes:
# Note On
track.append(mido.Message('note_on', note=note, velocity=100, time=0))
# 等待 duration 秒(以 tick 为单位,假设 480 ticks/beat)
ticks = int(duration * tempo * 480 / 60)
track.append(mido.Message('note_off', note=note, velocity=0, time=ticks))
mid.save(filename)
print(f'MIDI 文件已保存: {filename}')
# 示例:C大调音阶(C4 到 C5)
notes = [
(60, 0.5), # C4
(62, 0.5), # D4
(64, 0.5), # E4
(65, 0.5), # F4
(67, 0.5), # G4
(69, 0.5), # A4
(71, 0.5), # B4
(72, 1.0), # C5(长音)
]
create_midi_file('scale.mid', notes)package main
import (
"fmt"
"os"
"time"
"gitlab.com/gomidi/midi/v2"
"gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
)
// 需要安装:go get gitlab.com/gomidi/midi/v2
// 示例:列出 MIDI 输出设备并发送一个音符
func main() {
// 初始化 MIDI 驱动
drv, err := rtmididrv.New()
if err != nil {
fmt.Fprintf(os.Stderr, "初始化 MIDI 驱动失败: %v\n", err)
os.Exit(1)
}
defer drv.Close()
// 获取所有输出端口
outs := drv.Outputs()
if len(outs) == 0 {
fmt.Println("未找到 MIDI 输出设备")
os.Exit(0)
}
// 打开第一个输出设备
out, err := outs[0].Open()
if err != nil {
fmt.Fprintf(os.Stderr, "打开输出设备失败: %v\n", err)
os.Exit(1)
}
defer out.Close()
// 发送 Note On(通道 1,音符 C4=60,力度 100)
err = out.Send([]byte{0x90, 60, 100})
if err != nil {
fmt.Fprintf(os.Stderr, "发送 Note On 失败: %v\n", err)
os.Exit(1)
}
fmt.Println("发送 Note On: C4")
// 保持 500ms
time.Sleep(500 * time.Millisecond)
// 发送 Note Off(通道 1,音符 C4=60,力度 0)
err = out.Send([]byte{0x80, 60, 0})
if err != nil {
fmt.Fprintf(os.Stderr, "发送 Note Off 失败: %v\n", err)
os.Exit(1)
}
fmt.Println("发送 Note Off: C4")
}
8 个高频疑问
「制作工具」下的其他工具