HandPiano 最早不是从一份完整 PRD 开始的。它更像一个很直接的直觉:如果把 iPhone 或 iPad 的整块横屏变成一张可以发声的平面,用户是不是可以不用理解传统钢琴键,也能立刻用手指摸到旋律?

这个想法里真正有价值的地方,不是“在屏幕上放一排琴键”。那样会很像传统乐器的缩小版,既受限于屏幕宽度,也要求用户先接受一套已有的演奏规则。HandPiano 选择了另一条路:横向代表音高,纵向代表力度。用户不需要先找 C、D、E,也不需要先理解八度,只要把手指放上去,就能听见一个声音;向右滑,音高变高;向上推,声音更有力。

HandPiano gameplay

这篇日志记录的是它从零到现在的过程。这里既有产品判断,也有工程取舍,还有一段持续借助 Codex 推进项目的真实经验。

1. 第一件事:先让触摸真的变成声音

早期阶段的目标非常朴素:做出一个可以玩的最小闭环。

这个闭环包括三件事:

  • 用户触摸屏幕时,系统能够识别触点。
  • 触点的横向位置可以映射到音高。
  • 触点的纵向位置可以映射到力度,并即时发声。

这一步看起来简单,但它决定了整个项目后面的产品气质。HandPiano 没有把自己做成“虚拟钢琴键盘”,而是把屏幕定义成一张连续的声音地图。这个选择让用户的动作从点击按钮变成了探索空间:点、按、滑、停顿、多指同时触摸,都可以被解释为演奏动作。

工程上,早期分层也围绕这个闭环展开。PianoTouchSurface 负责触摸、坐标和视觉反馈,PianoAudioEngine 负责实时发声,PianoKeyLayout 负责把位置转成音高。这个结构没有一开始就追求复杂架构,而是先保证核心路径足够短:手指落下,声音响起。

Codex 在这个阶段的价值,是快速把项目入口、触摸生命周期和音频调用链串起来。尤其是在处理触摸结束、取消、多指切换这些边界时,它可以先读代码,再帮忙把 active touch、voice stop、视觉残影这些状态对齐。人负责判断“这个交互是不是像乐器”,Codex 负责把一次次判断落回代码。

2. 声音体验经历了多轮推翻

HandPiano 的声音不是一次调出来的。

最早的声音更接近电子合成,响应快,但缺少真实乐器的质感。随后项目尝试过更柔和、更木质的钢琴音色,也尝试过加入不同 instrument preset,让应用看起来像一个多乐器触控面板。但随着体验推进,方向逐渐变清楚:HandPiano 的核心不是“切换很多音色”,而是把一个钢琴触控体验做稳。

于是项目经历过几轮明显的音频取舍:

  • 先用合成音色跑通实时触发。
  • 再尝试增强钢琴音色和多乐器 preset。
  • 真机测试发现部分高音、重力度、多指叠加时会出现毛刺和失真。
  • 引入 voice pool、释放淡出、滤波和 soft limit,控制声音堆积。
  • 最后回到更专注的钢琴方向,并加入 Salamander Grand Piano 采样子集。

这是一段很典型的产品工程过程:不是每次新增都代表进步,有些方向做出来以后,反而帮助团队确认应该删掉什么。

采样钢琴加入以后,HandPiano 的声音质感明显更可信。它不再只是“屏幕发出一个音高”,而更像一件被压缩进移动设备里的乐器。不同力度层的采样让纵向手势有了实际意义:轻触和重按不只是音量差异,也有不同的声音质感。

这部分最依赖人的主观反馈。Codex 可以调整代码、组织采样、修编译错误、优化资源索引,但它不能替代用户在真机上听到的那一下“刺耳”或“舒服”。实际协作中,最有效的方式是:人用自然语言描述听感,Codex 把描述翻译成工程改动,然后重新构建验证。

3. 从一个玩具,变成可以留下作品的工具

当触摸和声音稳定后,产品自然遇到下一个问题:用户弹完以后,留下些什么?

如果 HandPiano 只是一个发声平面,它会很有趣,但生命周期很短。用户玩几下,听到声音,然后离开。录制和曲库让它从“玩具”变成了“工具”:用户可以保存一次演奏,可以回放,可以重命名,可以删除,也可以从内置演奏中获得灵感。

这里有一个关键工程选择:录制的不是一段音频文件,而是演奏事件。

每个演奏事件记录的是:

  • 时间点。
  • 触摸 ID。
  • 触摸阶段,例如开始、移动、结束。
  • 横向和纵向比例坐标。

这样做的好处是,回放不是简单播放一段声音,而是重新执行一次手指运动。应用可以在回放时重新发声,也可以重新画出触点、轨迹和涟漪。对一个以“触摸空间”为核心的产品来说,这比纯音频更符合产品本质。

后来演奏库逐步加入了内置曲目、本地录制、折叠分组、删除、重命名、播放、视频导出和分享。这些能力看起来分散,但背后都服务于同一个目标:让用户的演奏不是一次性的。

Codex 在这一阶段承担了大量“产品工程粘合剂”的工作。比如把内置演奏和本地演奏放进同一套模型,把删除和重命名做成一致的反馈,把曲库界面从简单列表推进到更接近产品形态的 overlay,再把导出能力接入现有回放系统。很多改动不是算法难题,而是需要在多个文件之间保持状态、命名、交互和数据结构一致。这个场景非常适合 Codex 辅助推进。

4. 节拍器让产品从“能弹”走向“能练”

音乐应用如果只关注发声,很容易停在娱乐层。真正让 HandPiano 开始变成练习工具的,是节拍器。

早期可以想到的方案是声音节拍器,也就是常见的 click。做出来以后,问题也很明显:用户正在弹钢琴,节拍器如果也发声,就会和演奏本身争抢注意力。特别是这个应用强调触摸、滑动和声音反馈,额外的 click 会让体验变得拥挤。

所以节拍器方向后来转向视觉和触感:

  • 工具栏跟随节拍轻微变化。
  • 节拍以水波纹形式从界面中心扩散。
  • 强拍用更明确的视觉层级提示。
  • 支持 BPM、拍号、重音、触感反馈和节拍音设置。
  • 内置演奏可以带上 metronome metadata,播放时与节拍同步。

这次迭代里最重要的产品判断是:节奏提示不一定要抢声音通道。对于 HandPiano 来说,视觉节拍器更像一层呼吸感,让用户在演奏时还能感知时间结构。

后面跟随练习系统也是从这里长出来的。节拍器提供时间轴,练习曲目提供目标,评分系统提供反馈,历史记录提供复盘。至此,HandPiano 从“自由弹奏”进入了“有目标地练习”。

5. 跟随练习:把抽象音乐能力拆成可完成的小任务

练习功能不是简单地把几首歌塞进列表。它更像一个产品化的学习路径。

现在项目里的练习被拆成几类路线:

  • 节奏路线:稳定拍、休止、长短节奏、切分、三连音。
  • 旋律路线:高低回应、三音模仿、上行下行、跳进、双指和弦、琶音。
  • 歌曲路线:短乐句和完整短歌,例如小星星、欢乐颂、摇篮曲等。

这套设计的关键,是把“我不会弹”拆成“我先完成这一小段”。用户不需要一开始就弹完整作品,而是先学会跟着提示在正确时间、正确区域触摸。每次练习结束后,系统从节奏准确度、稳定性和完成度几个角度给出结果,并把练习历史保存下来。

回放对照是这个功能里很有产品价值的一环。用户不只是看到一个分数,还能看到自己的触点和目标提示之间的关系:是慢了,是早了,是位置偏了,还是力度不稳定。对初学者来说,这种可视化比一句“再试一次”更有帮助。

工程上,练习系统拆出了 PracticeExercisePracticeSessionModelPracticeScorerPracticeHistoryStorePracticeReplayView 等模块。它们分别负责题目、流程、评分、持久化和回放。这个拆分让练习逻辑可以被单元测试覆盖,也让后续扩展练习内容时,不必每次都重写界面流程。

6. 首次教学解决的是“用户敢不敢开始”

一个新形态的乐器,最大的问题不是功能够不够,而是用户第一次打开时会不会愣住。

HandPiano 的屏幕上没有传统琴键,没有明确的按钮阵列,也没有“从这里开始”的天然暗示。因此首次教学不是附属功能,而是产品入口的一部分。

现在的首次教学把核心动作拆成几个步骤:

  • 轻点屏幕,理解触摸会发声。
  • 横向滑动,理解位置和音高的关系。
  • 改变纵向位置,理解力度和音色层次。
  • 使用双指,理解和弦或双声部。
  • 进入练习,理解应用不仅能自由玩,也能跟随训练。

这套引导后来又经过多次微调,例如 debug 构建中默认可重复展示,release 构建中只在首次完成或跳过前持续展示;用户也可以从菜单里重新打开。最新的 onboarding 和曲库回放流程也做了衔接,让用户从教学进入真实内容时更顺。

这类工作很容易被低估。它不是“写几个弹窗”,而是在设计用户第一次建立心智模型的路径。Codex 在这里的作用,是把用户体验反馈快速转成状态机、视图层和测试用例的调整;人则判断每一步是否自然,是否过度解释,是否真的让用户愿意继续摸一下屏幕。

7. 横屏体验不是小细节,而是产品边界

HandPiano 的核心体验依赖横屏。横屏给了音高足够的横向空间,也让双手操作更接近一个乐器表面。

但 iPhone 和 iPad 的真实使用环境很复杂。用户可能竖着拿设备,可能开着旋转锁,系统窗口状态也不总是如产品想象中那样干净。项目曾经踩过一个典型问题:如果只在系统层声明横屏,设备竖着时界面表现并不一定符合预期,甚至会出现提示页只占据部分窗口的情况。

后来的解决方案是把方向体验产品化:

  • 应用支持必要方向,让系统窗口状态可控。
  • 竖屏或窗口不稳定时,优先展示横屏引导。
  • 琴盘在横屏稳定后再淡入,避免旋转过程露出不完整界面。
  • 横屏提示优先级高于教学、练习和 overlay,保证入口状态明确。

这个改动说明一个产品工程原则:平台状态本身也是体验的一部分。尤其是一个依赖传感器、方向、触摸和音频的应用,不能只在理想状态下工作。

8. 本地化、截图、网页和 README:把工程变成可被理解的产品

随着功能增加,项目不再只需要代码,还需要表达。

HandPiano 现在有中英文 Localizable.xcstringsInfoPlist.xcstrings,README 也从简单说明更新为包含截图、核心能力、隐私说明、工程结构和构建方式的产品文档。项目还维护了 html 目录,用于双语主页和产品展示,并使用真实 app 截图替换早期素材。

这些工作看似和核心功能无关,但对一个产品很重要。因为当功能足够多以后,用户、开发者、评审者都需要快速理解:

  • 这是什么。
  • 它解决什么体验问题。
  • 当前已经做到什么程度。
  • 哪些能力是稳定产品,哪些还是探索原型。
  • 如何构建、测试和继续开发。

比如 README 里明确写到,相机手势识别仍是探索性实现,当前产品体验是触控优先。这种表达可以避免误解,也能帮助后续开发保持方向感。

9. 测试与现代化:让快速迭代不变成技术债

项目中后期开始补充单元测试,覆盖节拍器、练习评分、练习历史、演奏库、采样曲目和教学流程。与此同时,工程也逐步启用更完整的 Swift 并发检查,按现代 SwiftUI 和 Swift Testing 的方式整理核心逻辑。

这一步的意义不是为了“看起来工程化”,而是为了守住快速迭代的下限。

HandPiano 的很多逻辑都和时间有关:节拍推进、触摸事件、录制回放、练习评分、onboarding 状态切换。如果没有测试,每一次视觉或交互微调都可能悄悄破坏核心行为。把这些逻辑沉到可测试的 model 或 scorer 里,能让 UI 继续快速调整,同时不牺牲基础正确性。

Codex 在测试上的价值也很明显。它能根据现有模型快速补测试,能在改动后跑构建和测试,也能根据失败日志定位回归原因。更重要的是,它能帮助把“这个行为以后不要坏掉”转成一条具体测试。

10. Codex 协作经验:它更像产品工程搭档,而不是一次性生成器

这个项目使用 Codex 的最大收获,不是某一次生成了多少代码,而是形成了一种高频协作节奏。

比较有效的方式有几类。

第一,让 Codex 先读项目,再动手。每次进入新模块前,先用 rgsedgit loggit diff 看现状。这样它会沿着已有结构工作,而不是凭空创造一个看似合理但不贴合项目的方案。

第二,把需求说成体验问题,而不是只说实现手段。比如“节拍器声音会抢演奏空间”比“改一下 metronome”更有用;“竖屏时不要露出白底”比“修一下旋转”更明确。Codex 对工程路径很敏感,但产品方向仍然需要人给出。

第三,小步提交。这个项目的提交历史很清楚,从初始版本、音色调整、手势识别、录制曲库、视觉节拍器、横屏引导、采样钢琴、首次教学、本地化、单元测试、跟随练习、历史回放、视频导出,一路都有节点。AI 让改动速度变快,越是这样越需要版本边界。

第四,人保留审美和判断。声音是否舒服、练习是否有成就感、提示是否啰嗦、按钮位置是否顺手,这些不是编译器能回答的问题。Codex 可以给出实现选项,但最终要由产品体验决定。

第五,接受探索中的回滚。项目中曾经有增强音频模块,也曾经有多乐器 preset,后来有的被回滚,有的被移除。这不是失败,而是探索型产品的正常路径。Codex 降低了试错成本,但不应该让团队舍不得删除错误方向。

11. 从零到现在,HandPiano 实际完成了什么

到目前为止,HandPiano 已经从一个“触摸发声”的原型,发展成一个相对完整的本地音乐练习应用。

它现在具备:

  • 全屏横向触控琴面。
  • 横向音高映射和纵向力度映射。
  • 多指触摸、滑音、和弦、触摸涟漪和演奏轨迹。
  • 基于 Salamander Grand Piano 采样的钢琴声音。
  • 自由演奏、录制、保存、回放、重命名和删除。
  • 内置演奏库、本地作品库、折叠分组和视频导出。
  • 视觉节拍器、BPM 设置、拍号、重音、触感反馈和节拍音设置。
  • 首次启动交互式教学。
  • 跟随练习路线、练习评分、历史记录和对照回放。
  • 中英文本地化、Dynamic Type 和辅助功能标签。
  • README、截图、网页展示和项目复盘文档。
  • Swift Testing 单元测试覆盖核心逻辑。

这些能力共同构成了一个清晰的产品主张:HandPiano 不是把传统钢琴塞进屏幕,而是让屏幕成为一种新的触控乐器;它既可以让用户马上玩,也能逐步引导用户练。

12. 还没有完成的事

项目现在已经可玩、可录制、可练习,但还不是终点。

后续比较值得推进的方向包括:

  • 继续打磨音频延迟和真实设备上的动态响应。
  • 扩展更系统的练习课程,而不是只增加单个练习。
  • 让评分反馈更像教练,指出更具体的改进建议。
  • 优化 iPad 大屏上的双手演奏布局。
  • 重新评估相机手势识别是否应该回到主路径,或者继续保留为探索原型。
  • 强化导出内容的视觉表现,让分享出去的视频更像作品。

这些方向里,有些是工程问题,有些是内容问题,有些是产品定位问题。HandPiano 后续能走多远,不只取决于功能数量,而取决于它能否持续让用户在第一次触摸后,还愿意第二次打开。

结语

HandPiano 的历程很像一个小型产品从原型到成形的缩影。

最开始只是一个手指落点和一声钢琴音。后来它有了触摸轨迹,有了录制,有了曲库,有了节拍,有了练习,有了评分,有了教学,有了可以展示给别人看的网页和截图。每一步都不算宏大,但连起来以后,产品的轮廓就出现了。

Codex 在这个过程中扮演的角色,不是替代产品判断,也不是一次性生成完整应用。它更像一个一直在旁边的产品工程搭档:能读代码,能改代码,能跑构建,能补测试,能根据反馈继续调整。人负责方向、手感、审美和取舍;Codex 负责把这些判断快速变成可以运行的东西。

这也是这段经历最值得保留下来的经验:当 AI 把工程试错成本降下来以后,真正重要的能力反而更像产品能力。你要知道什么值得试,什么应该删,什么时候继续打磨,什么时候承认方向不对。HandPiano 能从零走到今天,靠的不是一次灵感,而是这种一轮又一轮的判断、实现、体验和修正。

References