简易钢琴完整源码,HarmonyOS 触摸发音乐器封装


实例:简易钢琴(Piano)|技术:全量代码解读、操作剧本、触摸乐器模式抽象
一、本篇定位
前两篇分别讲了几何层与交互层,本篇完成三件事:
- 全量源码逐段解读——PianoPage 从头到尾过一遍;
- 运行效果演示——从音阶弹奏到静音开关的完整操作;
- 模式抽象——"触摸发音乐器"通用模板,附两个改写场景。
二、完整源码:PianoPage.ets
/**
* 简易钢琴 —— 自定义 Canvas(CanvasRenderingContext2D)应用 10
* 对应博客:Canvas_2d/10_简易钢琴/10-1 ~ 10-3
*
* 功能:
* 1. 钢琴键盘:一个八度(C3~B3 白键 7 + 黑键 5)
* 2. Canvas 琴键绘制:白键/黑键布局,按下高亮
* 3. 触摸发音:Down 命中琴键 → 高亮 + AVPlayer 播放音阶
* 4. 音高提示:显示当前按下音符名
*/
import { audio } from '@kit.AudioKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'Piano';
/** 白键(C 大调音阶) */
const WHITE_KEYS: string[] = ['C3', 'D3', 'E3', 'F3', 'G3', 'A3', 'B3'];
/** 黑键位置:白键之间的间隔序号 */
const BLACK_POS: number[] = [1, 3, 5, 6, 7];
/** 黑键音符名 */
const BLACK_NAMES: string[] = ['C#3', 'D#3', 'F#3', 'G#3', 'A#3'];
@Entry
@Component
struct PianoPage {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private canvasW: number = 0;
private canvasH: number = 0;
private whiteW: number = 44;
private whiteH: number = 200;
private blackW: number = 26;
private blackH: number = 126;
private startX: number = 10;
private players: Array<audio.AudioPlayer | null> = [];
private muted: boolean = false;
@State pressedKey: string = '';
@State noteText: string = '触摸琴键弹奏';
@State mutedText: string = '';
aboutToDisappear(): void {
for (const p of this.players) {
if (p !== null) {
p.release();
}
}
this.players = [];
}
// whiteX / blackX / playNote / onTouch / hitKey / draw / build
// (见上两篇逐段解读)
}
逐段解读总表
| 代码段 | 职责 | 关键决策 |
|---|---|---|
| WHITE_KEYS | 白键音名 | C 大调八度 |
| BLACK_POS | 黑键间隔序号 | 布局参数化 |
| BLACK_NAMES | 黑键音名 | 升号记谱 |
| 几何常量 | whiteW/blackW 等 | 键盘比例 |
| whiteX/blackX | 坐标计算 | 白等宽黑跨交界 |
| hitKey | 命中判定 | 先黑后白 + Y 分区 |
| playNote | 发音(降级日志) | 无资源不报错 |
| draw | 键盘绘制 | 按下高亮 |
| onTouch | 三态交互 | Down/Move 弹奏 |
| 播放器数组 | 资源管理 | release 清理 |
| 静音开关 | muted | 演示控制 |
三、接入工程三步
Step 1:放文件
PianoPage.ets→entry/src/main/ets/pages/canvas_2d/
Step 2:注册路由(main_pages.json)
"pages/canvas_2d/PianoPage",
Step 3:首页入口(Index.ets)
Button('🎹 38 简易钢琴')
.fontSize(14)
.width('80%')
.onClick(() => {
this.getUIContext().getRouter().pushUrl({ url: 'pages/canvas_2d/PianoPage' });
})
四、运行效果演示(操作剧本)
场景一:首次打开
| 步骤 | 界面表现 | 数据来源 |
|---|---|---|
| 1 | 标题栏:“🎹 简易钢琴”,提示"触摸琴键弹奏",🔊 | — |
| 2 | 画布:白底 7 个白键(C3~B3 带标签),交界处 5 个深色黑键 | draw |
| 3 | 底部说明:“C3 D3 E3 F3 G3 A3 B3 · 黑白键触摸弹奏” | — |
场景二:C 大调音阶弹奏
依次点白键 C3→B3:
- 每个键按下瞬间变浅蓝、提示"♩ C3"、“♩ D3”…;
- 抬起恢复白色、提示复位;
- 画布日志输出 “play note: C3” 等(无资源环境)。
场景三:黑键弹奏
点 C3-D3 之间的黑键 → 深蓝高亮 + “♩ C#3”。五个黑键都能触发。
场景四:滑动滑音
手指从 C3 滑到 G3 → 沿途白键逐个点亮(Move 连续命中),松开全部复位。
场景五:静音开关
点 🔊 → 变 🔇 → 再弹琴键:高亮与提示正常、无发音日志(muted 短路)。再点恢复。
五、模式抽象:触摸发音乐器通用模板
5.1 模板骨架
┌─────────────────────────────────────────────┐
│ 键位几何层 │
│ 键数组(名称/位置编码)+ 坐标计算函数 │
│ 白等宽排布、黑跨交界(间隔序号编码) │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 命中判定层 │
│ 先查覆盖层(黑键)→ 再查底层(白键) │
│ Y 分区限定各键有效高度 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 反馈层 │
│ 视觉:按下高亮(pressedKey 比较) │
│ 听觉:playNote(AVPlayer / 降级日志) │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 生命周期层 │
│ 播放器数组懒创建 + release 统一清理 │
└─────────────────────────────────────────────┘
5.2 六个约定
- 键位参数化:白键等宽、黑键间隔序号——调整几何改常量即可;
- 先查覆盖层:黑键盖白键,命中先黑后白;
- Y 分区判定:黑键半高、白键全高,各限区域;
- 视觉听觉分离:高亮(绘制)+ 发音(播放)各自独立,静音只关声音;
- 声音可降级:无资源环境降级为日志,功能不报错;
- 资源统一清理:播放器数组 release,页面销毁不留泄漏。
5.3 两个改写场景
场景 A:鼓垫(Drum Pad)
| 钢琴实例 | 鼓垫改写 |
|---|---|
| 白键 7 个 | 打击垫 4×4 网格 |
| 黑键 | 特殊音效垫 |
| 命中黑键优先 | 网格矩形命中(无覆盖层) |
| playNote | 播放鼓点采样 |
场景 B:MIDI 键盘
| 钢琴实例 | MIDI 改写 |
|---|---|
| 一个八度 | 88 键(八度模式重复) |
| playNote | MIDI 事件输出(外部音源) |
| 按下高亮 | 力度感应(按压深度→音量) |
场景 C:木琴/竖琴 App——键位几何 + 触摸发音,模板复用。
三个场景的共同点:“键位几何 + 命中判定 + 视觉听觉反馈 + 资源生命周期”。掌握 38,任何"触摸发音乐器"都能起步。
六、三篇内容总对照
| 篇章 | 核心知识点 | 一句话总结 |
|---|---|---|
| 38-1 琴键布局与触摸演奏 | 黑键间隔编码、先黑后白 | 几何参数化 + 分区命中 |
| 38-2 页面UI与弹奏交互 | 双状态量、静音开关、播放器管理 | 一事件双通道反馈 |
| 38-3 完整代码与运行效果 | 全量源码、操作剧本、乐器模板 | 触摸乐器四层结构 |
七、FAQ(三篇汇总补遗)
Q1:38 与 21-37 的最大不同是什么?
A:引入声音维度——视觉 + 听觉双通道反馈。此前全是纯视觉应用。视听结合是乐器类应用的特征,也是 Canvas 应用的新方向。
Q2:真机怎么接入真实音效?
A:三步——① 准备音符 wav 到 resources/rawfile/notes/;② audio.createAudioPlayer() 建播放器;③ player.src = 'rawfile:///notes/C3.wav' + player.play()。替换 playNote 即可,其余零改动。接入点单一。
Q3:为什么黑键用升号(C#)而不是降号(Db)?
A:C 大调黑键习惯用升号记谱。记谱法约定——换调式(如 F 大调)可改降号。命名是显示问题,与实现无关。
Q4:按下高亮后为什么必须重绘?
A:Canvas 是"绘制后保持"的位图,不会自动更新。pressedKey 变化后必须 draw() 重画当前帧——Canvas 应用都要"状态变化 → 主动重绘"(全系列一致)。
Q5:滑音时 Move 高频触发会影响性能吗?
A:每帧 hitKey + draw(约 7 白 5 黑循环),开销小。高频触摸下 60fps 无压力。先测再优化。
Q6:能录下弹奏吗?
A:能——Down 时记录 {name, time} 数组,回放用定时器依次 playNote + 高亮。弹奏录制是教学好功能,留作练习。
八、结语
三篇文章完整走完了简易钢琴的"几何 → 交互 → 复用"闭环:
- 1 教你琴键建模:黑键间隔编码、先黑后白命中、按下高亮;
- 2 教你反馈设计:双状态量、静音开关、播放器生命周期;
- 3 教你模式复用:触摸乐器四层模板,改几何即覆盖鼓垫、MIDI。
更多推荐



所有评论(0)