2026/9/30 23:29:13

Codex 100个真实案例 - 用AI做在线钢琴模拟器(支持键盘演奏)

Codex 100个真实案例 - 用AI做在线钢琴模拟器(支持键盘演奏) 1. 从零做一个能弹的在线钢琴模拟器Codex 到底能帮到哪一步在线钢琴模拟器是什么简单说就是打开浏览器就能弹的钢琴电脑键盘映射琴键、按下实时发声、琴键跟着高亮鼠标和触屏也能点。它适合谁适合想练手 Web Audio API 的前端、想给孩子做个练琴小工具的人以及想用 Codex 这类 AI 编程助手跑一个完整小项目的人。我这次的目标很明确不装任何构建工具纯 HTML CSS JavaScript用 Codex 从零生成项目骨架再逐块补上音频引擎、键盘映射、录制回放和 MIDI 导出。整个过程里Codex 负责把重复的样板代码写出来我负责验证音准、延迟和交互手感。先说清楚这个项目的技术栈和边界。界面用 HTML5 CSS3 还原黑白键布局发声用 Web Audio API 的振荡器合成核心逻辑用原生 JavaScript多人合奏才需要一个简单的 Node.js WebSocket 服务。也就是说单人弹奏部分完全是静态页面双击index.html就能跑不需要服务器。环境检查只需要两条命令node -v # v20 即可 codex --version # 确认 Codex CLI 可用这里有个容易被忽略的点浏览器对音频有自动播放限制AudioContext必须在用户第一次交互点击或按键之后才能创建否则会一直处于 suspended 状态表现为“按了没声音”。所以音频引擎的初始化不能放在页面加载时要放在第一次noteOn里惰性执行。这一点后面在audio-engine.js里会具体处理。项目结构我让 Codex 一次性生成避免后面文件散落web-piano/ ├── index.html # 钢琴主页面 ├── css/ │ └── piano.css # 键盘样式 ├── js/ │ ├── audio-engine.js # Web Audio 音频引擎 │ ├── keyboard.js # 键盘映射与交互 │ ├── recorder.js # 录制与回放 │ ├── songs.js # 内置曲目 │ ├── midi-export.js # MIDI 导出 │ └── sustain.js # 延音踏板 └── server.js # WebSocket 多人合奏服务器生成这个结构的提示词可以直接复制codex 创建一个在线钢琴模拟器项目项目名 web-piano。包含 index.html、css/piano.css、js/audio-engine.js、js/keyboard.js、js/recorder.js、js/songs.js、js/midi-export.js、js/sustain.js、server.js。界面要精美模拟真实钢琴外观所有注释使用中文。Codex 执行时会自动建目录、建空文件然后逐个填充内容。实测下来它生成的骨架基本可用但音频参数和键盘映射的细节需要自己校对尤其是音名到频率的换算错一个半音整首曲子就跑调了。2. 用 TaoToken 给 Codex 配好模型通道再开始写代码Codex CLI 本身是客户端真正干活的是背后的大模型。如果你直接用官方通道可能会遇到额度、网络或计费上的麻烦。我的做法是把它接到 TaoToken 上用统一的 API 入口来跑模型切换和用量查看都在一个控制台里完成省去来回改配置。TaoToken 是什么它是一个大模型 API 聚合服务能做什么把不同模型的调用统一成一套 OpenAI 兼容接口适合谁适合需要长期用 Codex 写代码、又不想被单一模型绑死的开发者。官网入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 地址是 https://taotoken.net/api这个不加 UTM。配置分三步拿 Key、写配置、验证连通。第一步去控制台创建 API Key。打开 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 登录后在 API Keys 页面新建一个密钥复制出来先存好后面配置要用。注意 Key 只在创建时完整显示一次关掉页面就看不到了。第二步把 Key 写进 Codex 的配置。Codex CLI 读取的是~/.codex/config.toml如果你用的是兼容 OpenAI 接口的客户端也可以写auth.json。这里给一份可直接复制的 TOML 片段# ~/.codex/config.toml model gpt-4o model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat然后在 shell 里导出环境变量避免把 Key 硬编码进文件export TAOTOKEN_API_KEYsk-你刚才复制的Key如果你更习惯用auth.json的形式可以这样写{ OPENAI_API_KEY: sk-你刚才复制的Key, OPENAI_BASE_URL: https://taotoken.net/api }三件套要记牢Base URL 是https://taotoken.net/apiKey 是控制台生成的那串Model ID 按你实际要用的填比如gpt-4o或claude-3-5-sonnet。这三个值缺一个都连不上。第三步验证配置是否生效。跑一条最简单的请求codex 用一句话说明 Web Audio API 的作用如果返回正常文本说明通道通了。如果报 401多半是 Key 没导出或写错如果报连接超时检查base_url有没有多写斜杠。配置文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到字段不确定时对着看。配好之后Codex 每次生成代码都会走这条通道。我试过在同一个会话里连续让它写音频引擎和键盘映射上下文保持得不错不用反复贴需求。对于这种多文件小项目通道稳定比模型多聪明更重要——写到一半断流前面的上下文就白费了。3. 可复制的键盘映射与音频触发配置这一节是整篇文章的核心直接给能跑的代码。先看键盘映射表它决定了按哪个键发哪个音。我采用两行映射主行 A-L 对应中音区白键W-P 对应黑键Z/X 切换八度。映射表用对象存键是键盘字符值是音名加八度偏移标记// js/keyboard.js 中的映射配置 const KEY_MAP { // 白键 - 主行 a: C, s: D, d: E, f: F, g: G, h: A, j: B, k: C, l: D, ;: E, // 黑键 - 上行 w: C#, e: D#, t: F#, y: G#, u: A#, o: C#, p: D#, // 低八度白键 z: C-, x: D-, c: E-, v: F-, b: G-, n: A-, m: B- };表示高一个八度-表示低一个八度基准八度是 4。解析函数把标记还原成完整音名let currentOctaveOffset 0; function resolveNote(mappedNote) { const baseOctave 4 currentOctaveOffset; if (mappedNote.endsWith()) { return mappedNote.slice(0, -1) (baseOctave 1); } else if (mappedNote.endsWith(-)) { return mappedNote.slice(0, -1) (baseOctave - 1); } return mappedNote baseOctave; }音频引擎这边核心是频率表和 ADSR 包络。频率表按十二平均律算A4 440Hz 为基准buildFrequencyTable() { const noteNames [C,C#,D,D#,E,F,F#,G,G#,A,A#,B]; const frequencies {}; for (let octave 3; octave 5; octave) { noteNames.forEach((note, index) { const midiNumber (octave 1) * 12 index; frequencies[${note}${octave}] 440 * Math.pow(2, (midiNumber - 69) / 12); }); } return frequencies; }按下琴键时创建振荡器组钢琴音色用基频加二三次谐波叠加noteOn(note) { this.init(); const freq this.noteFrequencies[note]; if (!freq) return; const now this.ctx.currentTime; const gainNode this.ctx.createGain(); gainNode.gain.setValueAtTime(0, now); gainNode.connect(this.masterGain); // 钢琴音色基频 2次谐波 3次谐波 三角波 const osc1 this.ctx.createOscillator(); osc1.type sine; osc1.frequency.setValueAtTime(freq, now); osc1.connect(gainNode); osc1.start(now); const osc2 this.ctx.createOscillator(); osc2.type sine; osc2.frequency.setValueAtTime(freq * 2, now); const g2 this.ctx.createGain(); g2.gain.value 0.3; osc2.connect(g2).connect(gainNode); osc2.start(now); // ADSR 包络 gainNode.gain.linearRampToValueAtTime(0.8, now 0.005); gainNode.gain.linearRampToValueAtTime(0.3, now 0.305); this.activeNotes[note] { oscillators: [osc1, osc2], gainNode }; }松开琴键走释放阶段0.5 秒内衰减到零noteOff(note) { const data this.activeNotes[note]; if (!data) return; const now this.ctx.currentTime; data.gainNode.gain.cancelScheduledValues(now); data.gainNode.gain.setValueAtTime(data.gainNode.gain.value, now); data.gainNode.gain.linearRampToValueAtTime(0, now 0.5); data.oscillators.forEach(osc osc.stop(now 0.6)); delete this.activeNotes[note]; }键盘事件监听要处理长按重复触发的问题用e.repeat过滤document.addEventListener(keydown, (e) { if (e.repeat) return; const key e.key.toLowerCase(); if (key ) { sustainPedal.press(); return; } if (key z) { shiftOctave(-1); return; } if (key x) { shiftOctave(1); return; } const mapped KEY_MAP[key]; if (!mapped) return; playNote(resolveNote(mapped)); });这套配置的关键参数就三个attack0.005 秒决定起音干脆度release0.5 秒决定松键后的余韵谐波增益 0.3 决定音色明亮度。想调成风琴音色把谐波增益提到 0.8、release 缩到 0.1 就行。4. 本地启动后逐键验证音准与延迟代码写完不算完得实际弹一遍才知道对不对。启动方式有两种单人模式直接开静态服务cd web-piano npx serve . # 或者用 Python python3 -m http.server 8080多人合奏模式走 Node 服务node server.js # 钢琴服务器已启动: http://localhost:3000打开页面后按这个顺序验证。第一步验证单音发声。按 A 键应该听到 C4同时琴键高亮。如果没声音打开浏览器控制台看有没有AudioContext was not allowed to start的警告有的话说明初始化时机不对检查noteOn里有没有先调init()。第二步验证音准。连续按 A、S、D、F、G、H、J对应 C4 到 B4 的白键音阶听是否均匀上行。再按 W、E、T、Y、U 弹黑键C#4、D#4、F#4、G#4、A#4。如果某个音明显偏高或偏低多半是频率表里 MIDI 编号算错了重点检查(octave 1) * 12 index这个公式。第三步验证延迟。快速连按同一个键听声音是否跟手。Web Audio 的调度精度在毫秒级正常情况下感觉不到延迟。如果明显滞后检查是不是每次noteOn都重新创建了AudioContext——正确做法是全局只创建一个实例。第四步验证和和弦。同时按住 A、D、G应该听到 C4、E4、G4 三个音叠加而不是只响一个。这考验activeNotes对象是否按音名分别存储如果共用一个 gainNode 就会互相覆盖。第五步验证八度切换。按 X 键界面八度显示从 4 变 5再按 A 应该发出 C5。按 Z 键回到低八度。切换后琴键上的标签要同步更新否则说明buildPianoKeys没有重新渲染。第六步验证延音踏板。按住空格再弹几个音松开琴键后声音应该继续响松开空格所有延音中的音符一起衰减。如果松开空格没反应检查releaseSustainedNotes有没有被调用。第七步验证录制回放。点录制弹一小段停止点回放应该原样重现。注意回放时琴键也要跟着高亮这要求录制的事件里同时存了音名和动作类型。第八步验证 MIDI 导出。录一段后点导出浏览器下载.mid文件用任意 MIDI 播放器打开音高和节奏应该和录制一致。如果播放器报格式错误重点查 track length 那四个字节有没有算对。这八步走完基本能确认整个链路是通的。我踩过的坑是黑键定位黑键用绝对定位放在白键之间偏移量按whiteKeyIndex * 55 - 18算55 是白键宽度。如果改了白键宽度忘了改这个值黑键就会错位。5. 常见报错排查401、local proxy failed 与 reading choices接入和运行过程中报错基本集中在几类。下面按真实错误信息对照排查。401 Unauthorized。这个最常见出现在 Codex 请求模型时。原因通常是 Key 没生效。检查顺序echo $TAOTOKEN_API_KEY看环境变量有没有值config.toml里env_key字段名和导出的变量名是否一致Key 有没有多余空格。如果用的是auth.json确认OPENAI_BASE_URL写的是https://taotoken.net/api结尾不要带斜杠。local proxy failed / connection refused。这类报错说明客户端连不上配置的地址。先确认base_url拼写正确再确认本机网络能访问该域名。如果之前配过其他代理工具检查环境变量里有没有残留的HTTP_PROXY、HTTPS_PROXY它们会劫持请求。清掉再试unset HTTP_PROXY HTTPS_PROXY ALL_PROXYreading choices 相关报错。这通常出现在响应解析阶段提示cannot read property choices of undefined。原因是返回体不是预期的 OpenAI 格式可能是wire_api配错了。Codex 的config.toml里wire_api要设成chat对应/v1/chat/completions接口。如果设成responses而服务端不支持就会解析失败。OAuth 相关报错。如果你之前登录过官方账号本地可能残留了 OAuth token和 API Key 模式冲突。清理掉旧的凭证缓存重新用 Key 模式启动。具体路径在~/.codex/下把旧的 auth 文件备份后删除即可。音频相关的报错。The AudioContext was not allowed to start说明初始化时机不对必须等用户交互。Failed to execute start on AudioScheduledSourceNode说明同一个振荡器被 start 了两次检查noteOn里有没有对已存在的音符重复创建。WebSocket 连接失败。多人合奏时报WebSocket connection to ws://localhost:3000 failed先确认node server.js在跑再确认页面和服务器同源。如果页面是file://打开的WebSocket 的 hostname 会是空改成http://localhost:3000访问。排查时有个通用思路先看浏览器控制台再看终端输出最后看配置文件。三层信息对不上时优先相信终端——它离请求发起端最近。6. 把这条通道固定下来继续做下一个案例钢琴模拟器跑通之后你会发现真正花时间的不是写代码而是配通道和排查连接问题。把 TaoToken 这套配置固定下来后面再做类似的小项目就能直接复用同一个config.toml同一个环境变量换的只是提示词和项目名。如果你主要做这种短平快的验证型项目用按量计费的 API Key 就够了去 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 管理密钥配合接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 对照字段。如果打算长期用 Codex 写代码、跑 Agent 任务Coding Plan 会更划算入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。想先试试模型对话效果可以直接开 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发几条消息感受一下响应速度。回到项目本身还有几个方向可以继续让 Codex 帮你加五线谱实时渲染、跟弹教学模式、频谱可视化、真实钢琴采样替换合成音、PWA 离线安装。每一个都是独立的增量不用推倒重来。我的建议是先把当前这版跑稳把音准和延迟这两个硬指标确认到位再往上叠功能——地基不稳加得越多越乱。