过去十天,我把一个想法做成了能装进手机的完整应用:和弦助手(Chord Helper)。它不是只查和弦的工具,而是把「查和弦 → 调音 → 鼓机/伴奏 → AI 写 solo → 保存作品 → 继续对话改谱」串成了一条完整链路。整个 APK 约 227 KB,没有第三方 Android 依赖,代码在 GitHub: wenyuhaokikika/chord。
这篇文章记录几个工程决定:为什么用 WebView、和弦指法怎么穷举、手机上为什么会出现哑音、四个 agent 怎么保证不胡写,以及作品版本和双指板这些界面细节。
下载 APK
推荐(始终指向最新版):下载 ChordHelper-latest.apk(当前为 1.3.0,227 KB,versionCode 8)
固定本次版本:ChordHelper-latest.apk(固定 v1.3.0) · ChordHelper-1.3.0.apk
历史版本(同一 Release 的独立 APK 资产):
- ChordHelper-1.2.4.apk
- ChordHelper-1.2.3.apk
- ChordHelper-1.2.2.apk
- ChordHelper-1.2.1.apk
- ChordHelper-1.2.0.apk
- ChordHelper-1.1.0.apk
注意:GitHub Release 页面底部的 Source code (zip) / Source code (tar.gz) 是源码归档,不是 APK,不能直接安装。请点上面的 .apk 链接;SHA256 校验在 SHA256SUMS.txt。
它到底做了什么
- 12 个调 × 30 种和弦属性,穷举 0–24 品内的所有合法指法,按把位分组;
- 麦克风调音、调式音阶指板、七种教会调式;
- 13 种鼓机节奏 + 63 条和弦走向 + 14 种分解/扫弦节奏型,可同时播放;
- 四个手写 LLM agent 写即兴 solo,带本地校验器和任务队列;
- 根据场景提示词推荐鼓、节奏型、走向、速度和 Top5,用户确认后再生成;
- 每次创作保存成 craft:伴奏实例、旋律、推荐、用户选择、会话和版本记录;
- 作品详情页有两个吉他指板、和弦图、节奏格、三声部音量和分页版本表。
为什么是原生壳 + WebView
指板和和弦图本质是二维矢量绘制。用 SVG 描述,改一行刷新就能看到;乐理和算法全是纯 JS,可以脱离 Android 在无头浏览器里做断言。于是最终结构是:
Java Activity(WebView / 麦克风 / 文件选择 / SQLite / HTTP 桥)
↓
assets/www/index.html + CSS + ES5 JavaScript + SVG
↓
SQLite items(kind,id,json,updated_at) + settings
代价是要兼容老 WebView,所以 JS 全部用 ES5,不用箭头函数、模板字符串、Set 或 Object.assign;CSS 变量不支持时还有一层降级样式。换来的好处是零第三方依赖、APK 很小,而且所有业务逻辑都能在浏览器里测试。
和弦:穷举,而不是查表
指法搜索是一个带剪枝的 DFS:按弦枚举品位,实时检查音高集合、跨度、低音是否根音、发声弦是否连续、最少发声弦数。每个属性和弦只声明音程与必需音,不为每个和弦手写指法表。
实测 C 大三和弦找到 120 个指法,约 1–2ms;12 个根音 × 30 种属性共 81,073 个指法,全量跑完约 300ms。这个规模让“全把位展示”成为默认能力,而不是一个昂贵的附加功能。
但“能查到 120 个”不等于“用户能记住 120 个”。所以和弦页默认改成 CAGED 五种指型:C、A、G、E、D 五个代表指型卡,先记形状和根音位置(C/A 在 5 弦,G/E 在 6 弦,D 在 4 弦),点卡片直接在指板上高亮。完整的 120 个指法折叠进“全部指法(高级)”,需要时再展开。
CAGED 分组不是硬编码 30 种和弦图,而是从当前搜索出的合法指法里,为五个根音区域各挑一个代表:优先根音弦、把位接近、闭合形状和足够发声弦数。这样换调、换和弦属性时五张卡会跟着变;七/九/变化和弦的代表指型更接近“同把位内最合适的按法”,适合作把位参考。
伴奏与音频:一次只排一拍
最早的实时伴奏一次把整个和弦的扫弦排进 WebAudio 时间轴。4 拍、每拍 2 次扫弦、每次 6 根弦、每根弦 7 个泛音,再加上 chorus,瞬间会创建几百个振荡器。桌面上听不出问题,手机上就开始出现哑音、丢音、尾音消失。
修复不是简单调小音量,而是改调度粒度:一次只排 1 拍,Transport 每拍继续调用。每次最多创建几十个节点,和弦切换仍严格落在拍点。
beat = live.beatCursor;
chunk = min(1, currentChord.beats - currentChord.offset);
for (event of plan.events) {
if (event.chordIdx === currentChord.idx &&
event.beat >= beat && event.beat < beat + chunk) {
playEvent(...);
}
}
live.chordSeconds = chunk * 60 / bpm;
伴奏声部还改成 5 个泛音、关闭 chorus;主压缩器从 -10dB/4:1 放宽到 -7dB/3:1,solo 总线也减少压缩。目标是保留瞬态和清晰度,而不是把所有东西压成一块。
AI 即兴:四个 agent + 一个不信任模型的校验器
生成不是一次 chat completion,而是一条流水线:
- 主控:规划结构、动机、音区走向;
- 填音:写每小节 8 个八分格;
- 衔接:给滑音、推弦、击勾弦、揉弦;
- 润色:按动机发展、呼吸、句尾落点重写一版;
- 确定性校验器:按 Bias 修掉越界音、清理孤儿延音、补空小节、检查格式。
前四步可以失败、可以降级;最后一步永远不会“相信模型说自己遵守了规则”。比如 Bias ≤ 20 时,校验器会真的把非和弦音移到最近的允许音;连续三个相同音会被换掉;滑音/推弦要求前一个位置是实际拨响的音符。
提示词不为空时,先跑一个推荐 agent:从可用的鼓机、节奏型、走向 id 里选择,返回一个单独的推荐速度 bpm 和 bpmTop5。鼓、节奏、走向和速度都可以在面板里改,点速度或输入 BPM 只记录草稿,必须再点「用这些选择生成」才真正调用模型。这样不会出现“用户还在改配置,已经花了一次 token”的尴尬。
作品(craft):把一次创作冻成一条记录
一条 craft 存在 SQLite 的 items(kind='craft') 里,包含:提示词、推荐结果、用户选择、伴奏实例快照、简谱、作品会话,以及版本数组。
{
prompt,
recommendation: { bpm, bpmTop5, drumId, rhythmId, progressionId, reason },
chosen: { bpm, drumId, rhythmId, progressionId },
instance: { drum, rhythm, progression, keyPc, scaleId, bpm },
score,
versions: [{ no, note, settings, score, createdAt }],
finalVersionId,
messages: [{ role, text, versionId, at }]
}
每次会话修改成功都会追加一个版本,最终版本有明确标记;对话里的「预览 vN」可以直接切到对应谱面。版本表按每页 5 条分页,列出速度、鼓机、节奏型、走向、修改提示词和时间——它不只是历史记录,也是一次创作的配置快照。
界面上的几个小决定
- 调音不占页签:放在设置左边的按钮里,以全屏 dialog 打开,关闭时释放麦克风;
- 作品独立成 tab:即兴右边是作品库,即兴页只负责生成;
- 速度不是只有 Top5:一个推荐值 + 可输入数字框 + Top5 快捷项;
- 简谱按拍分组:每两格一拍,浅/深背景交替,不再看不清音落在第几拍;
- 八度点:数字上方/下方的圆点表示高/低八度,音名里也带八度可对照;
- 详情页的两块指板:左边旋律推荐指型,右边节奏和弦推荐把位,播放时跟着当前音更新;
- 返回按钮 sticky:作品详情滚到哪都能回到作品库。
测试:把声音和手势都变成断言
这个项目没有用截图验收,主要靠可断言的测试页:
- 引擎自检 156 条:乐理、指法合法性、SVG 坐标、性能;
- 即兴自检 150 条:mock 四个 agent、任务队列、Bias、指型、版本、八度点;
- 界面自检 357 条 × 4 种手机/桌面视口:点击、路由、弹窗、布局、版本分页;
- 调音端到端 43 条:注入已知频率,跑完整条音频到界面链路;
- 鼓机/伴奏 42 条:用 OfflineAudioContext 渲染真实波形,核对拍点、切音、音色和音量。
打包脚本还会逐字节比对 APK 内 assets 与源码,防止“代码改了但装的是旧包”。这一次就抓到过几次资源不一致。
已知限制
它仍然是合成音色,不是采样音源;调音针对单音;即兴固定 4/4、每小节 8 个八分格;AI 需要用户自己的 Key,Key 只存在本机,不写进 APK 或仓库。版本表在手机窄屏上需要左右滑动,但分页和最终版本标记是完整的。
如果你也在做“WebView + WebAudio + LLM”的移动端应用,欢迎直接看代码和测试页。项目地址:github.com/wenyuhaokikika/chord。README 讲怎么用,process.md 记录了更多算法、调度和踩坑细节。