01Context背景
Hardware constraints硬件约束
Six hardware constraints determine the rendering, audio and input design.渲染、音频和输入设计受六项硬件约束。

OCTAVE is firmware for the FoloToy AI Passport card: a tuner, a sustained-note scorer, vocal range, scales, chords, guitar and ukulele fingerings, a metronome, tap tempo, a sound visualiser, ear training and a drone. The card is the size of a credit card.OCTAVE 是 FoloToy AI Passport 卡片上的固件,有调音器、长音打分、音域、音阶、和弦、吉他与尤克里里指法、节拍器、敲击测速、声音可视化、练耳和持续音十一个工具。卡片和信用卡差不多大。
- Framebuffer memory. One frame is 240×320×2 = 153,600 B. Free heap at boot is about 125 KB.
- No FPU. A float multiply takes tens of cycles. A 64-bit division or square root uses a software library call taking hundreds of cycles.
- One core. Audio synthesis and rendering share the CPU. Synthesis time reduces the CPU time available for each frame.
- 16 KB flash cache. Hot functions reside in IRAM to reduce cache misses when a frame executes more code than the cache holds.
- Speaker response. Output is weak below 1 kHz (section 05).
- Three keys on one ADC. Key combinations are unavailable. The three keys support click, double-click and long-press.
- Framebuffer 内存。一帧 240×320×2 = 153,600 B,开机时空闲 heap 约 125 KB。
- 没有 FPU。浮点乘法需要几十个周期。64 位除法和开方通过软件库实现,每次调用需要几百个周期。
- 单核。音频合成和渲染共用 CPU。合成多用 1 ms,每帧可用的 CPU 时间就少 1 ms。
- 16 KB flash cache。热函数放在 IRAM 中,减少单帧执行代码超出 cache 容量时的 cache miss。
- 喇叭频响。1 kHz 以下输出较弱(见第 05 节)。
- 三个键共用一路 ADC。不支持组合键。三个键通过单击、双击和长按区分操作。
The requirements specified motion graphics, at least eight functions and firmware under 6 MB. A host simulator built on day one renders firmware frames for the promo.需求包括动效、至少 8 个功能和 6 MB 以内的固件。第一天完成的 host 模拟器直接渲染固件画面,供宣传片使用。
A build script scans all strings and generates 4-bit bitmaps for the 267 Chinese characters used. Sizes are 13 and 17 px, using about 68 KB of flash. A missing glyph fails the build.构建脚本扫描全部字符串,为实际使用的 267 个汉字生成 4 位 bitmap。字号为 13 和 17 px,占用约 68 KB flash。缺字会导致构建失败。
02Graphics画面
Strip rendering at 50 fps50 fps Strip 渲染
Eight strips cover the screen. Optimised arithmetic produces bit-identical results.屏幕分八个 strip 渲染。优化后的运算结果保持逐位一致。
CPU draws绘制 DMA sends发送
The screen uses eight 240×40 strips and two 19,200 B buffers. The CPU draws strip N while DMA sends strip N−1. The SPI interrupt releases each buffer through a counting semaphore.屏幕分成 8 个 240×40 的 strip,使用两块 19,200 B 的 buffer。CPU 绘制第 N 条时,DMA 发送第 N−1 条。SPI 中断通过 counting semaphore 释放 buffer。
Each strip starts from the background and replays the draw list. Draw calls return early when the shape does not intersect the current strip.每个 strip 都从背景开始重新执行 draw list。图形与当前 strip 不相交时,绘制函数直接返回。
Fixed-point arithmetic定点运算
Geometry uses Q16.16 fixed point. A full turn is 65,536 angle units; overflow wraps without a modulo operation. Sine uses a 1,024-entry table. Arithmetic optimisations preserve the exact output:几何使用 Q16.16 定点数。一整圈为 65,536 个角度单位,溢出直接回绕,无需取模。正弦使用 1,024 项查找表。运算优化保持输出逐位一致:
- 64-bit square root: about 3× faster, with exact equivalence checked on a million values;
- 64-bit division done as three 32-bit divisions, returning exactly what the library routine returns;
- when drawing a ring, each row's square root seeded from the row above.
- 64 位开方:速度约为基准实现的 3 倍,用一百万个值验证结果完全等价;
- 64 位除法拆成三次 32 位除法,结果和库函数完全一样;
- 画圆环时,每一行的开方从上一行的结果出发修正。
Byte comparisons and fuzzing verify arithmetic equivalence and pixel output.逐字节比对和 fuzz 测试验证运算等价性及像素输出。
Edge coverage and blending边缘 coverage 与混色
Each row has an outer span of possible coverage and an inner span of full coverage. The inner span uses a solid fill. The signed distance field runs on edge pixels. One multiply blends all three RGB565 channels:每行提供可能覆盖的 outer span 和完全覆盖的 inner span。inner span 直接填色,边缘像素计算 signed distance field。一次乘法完成 RGB565 三个通道的混色:
uint32_t bg = (dst | dst << 16) & 0x07E0F81F; // spread R, G, B apart
uint32_t fg = (src | src << 16) & 0x07E0F81F;
uint32_t r = ((((fg - bg) * a) >> 5) + bg) & 0x07E0F81F; // a = 0..32
return r | r >> 16;
Alpha and coverage are quantised to 33 levels. The caches in section 06 use these 33 steps.Alpha 和 coverage 均量化为 33 级。第 06 节的 cache 使用这 33 级数值。
03Motion动效
Spring animationSpring 动画
Interactive motion preserves position and velocity when the target changes.交互动效在目标改变时保持位置和速度连续。
Damped springs drive home scrolling, the selection indicator, rolling digits and the QR reveal. Fixed-point integration uses 4 ms sub-steps. Position and velocity remain continuous when the target changes.主页滚动、选择指示器、滚动数字和 QR 显示使用阻尼 spring。定点积分采用 4 ms 子步长。目标改变时,位置和速度保持连续。
| Preset预设 | Frequency频率 | Damping ζ阻尼 ζ | Used for用途 |
|---|---|---|---|
| SNAP | 5.5 Hz | 0.80 | most UI大部分界面 |
| SOFT | 2.6 Hz | 0.95 | large moves大范围移动 |
| BOUNCE | 6.5 Hz | 0.45 | accent animations强调动效 |
| NEEDLE | 4.0 Hz | 0.62 | the tuner needle调音器指针 |
- Selection indicator. Its ends use separate springs at 9.0 Hz and 4.2 Hz with the same target. The frequency difference changes its width during movement.
- Rolling digits. Each digit uses a spring with a different frequency. Simultaneous digit changes reach their targets at staggered times.
- Carousel wrap. At the last-to-first boundary, all three springs shift by one full cycle. This preserves the frame and leaves a single-step transition.
- Circular transition. A circle expands from the tool icon over 560 ms. The tool runs from the start of the transition and renders live content inside the circle.
- Luminance lookup. A constant table in flash eliminates runtime
powftable generation, which takes about 18 ms without an FPU.
- 选择指示器。两端分别使用 9.0 Hz 和 4.2 Hz 的 spring,目标相同。频率差使指示器宽度在移动过程中变化。
- 滚动数字。每一位数字使用不同频率的 spring。同时变化的数字在不同时刻到达目标。
- 轮播循环。从最后一项切回第一项时,三个 spring 整体平移一个完整周期。画面保持不变,转场距离为一项。
- 圆形转场。圆从工具图标处向外扩展,用时 560 ms。工具从转场开始时运行,圆内显示实时画面。
- 亮度查表。亮度常量表存储在 flash 中,无需在运行时用
powf建表。无 FPU 时,建表耗时约 18 ms。
04Pitch detection音高检测
Integer pitch detection整数音高检测
YIN uses 32-bit integers. Long-lag refinement reduces interpolation error by a factor of up to 16. Measurements use synthetic signals.YIN 使用 32 位整数。长 lag 精修将插值误差最多缩小 16 倍。测量使用合成信号。
- Level gate电平门限
- Low-pass, 16 → 8 kHz低通,16 → 8 kHz
- YIN, integer整数 YIN
- 2 octave guards两道八度保护
- Back to 16 kHz回到 16 kHz
- Long-lag refinement长 lag 精修
Each call reads 1,024 samples (64 ms). Normalisation to ±1000 keeps the YIN inner loop within 32-bit integers. The normalised difference is computed on demand and stops at the first required lag.每次读取最近 1,024 个采样(64 ms)。归一化到 ±1000 后,YIN 内层循环使用 32 位整数。归一化差分按需计算,到首个所需 lag 时停止。
Octave guards. Sung vowels often have a weaker fundamental than their second or third harmonic. A deeper dip at twice the period can override the first dip. The previous result is retained as a hint only if its octave also passes. Deliberately wrong hints at 48 frequencies produce zero octave errors.八度判定。唱元音时,基频常弱于二、三次谐波。两倍周期处更深的差分谷可替代首个谷。上次结果仅在其八度判定通过时才保留为 hint。测试在 48 个频率上输入错误 hint,八度错误为零。
Fractional-period analysis. At 8 kHz, 1,200 Hz spans 6.67 samples per period and 20.0 samples over three periods. Analysis at 16 kHz tests fractional periods p/k for k = 6 down to 2.分数周期分析。8 kHz 采样下,1,200 Hz 的单周期为 6.67 个采样,三个周期为 20.0 个采样。算法在 16 kHz 采样上检验分数周期 p/k,k 从 6 递减至 2。
Long-lag refinement. At 16 kHz, a parabola fits the difference function at k times the period. The two passes place the lag near 150 and 600 samples. Dividing lag by k also divides interpolation error by k: at 440 Hz, by 16.长 lag 精修。在 16 kHz 采样上,对 k 倍周期附近的差分函数做抛物线拟合。两次拟合的 lag 分别接近 150 和 600 个采样。lag 除以 k 得到周期,插值误差也除以 k:440 Hz 时除以 16。
| Host testHost 测试 | Limit断言 | Worst最差 |
|---|---|---|
| Sines 60–1200 Hz, 48 log-spaced正弦 60–1200 Hz,对数间隔 48 点 | < 1¢ | 0.009¢ |
| Vowels a / i / u, weak fundamental元音 a / i / u,基频弱 | < 1¢ | 0.064¢ |
| Vowel + white noise, SNR ≈ 11 dB元音 + 白噪声,SNR ≈ 11 dB | < 6¢ | 4.39¢ |
| Signal at −40 dBFS−40 dBFS 的弱信号 | < 1¢ | 0.019¢ |
| One-octave glide in 0.5 s0.5 s 内滑一个八度 | 0 octave errors0 次八度错误 | 0 |
| White noise白噪声 | no confident pitch不报确定音高 | 0 / 10 |
Log-spaced test frequencies cover non-integer periods. Vibrato estimates within the analysis window’s instantaneous-frequency range count as exact. Each call takes about 4–6 ms in QEMU. Tools run detection at most every 32 ms.测试频率按对数间隔选取,覆盖非整数周期。颤音估计落在分析窗内的瞬时频率范围即计为零误差。QEMU 中每次调用约 4–6 ms。各工具的检测间隔至少为 32 ms。
Tuner hysteresis requires a shift above 0.62 semitone and two matching readings to change notes. Lock requires ±3 cents for 300 ms. Unlock occurs beyond ±6 cents.调音器通过 hysteresis 稳定读数。换音要求偏移超过 0.62 个半音,且连续两次读数一致。在 ±3 音分内保持 300 ms 后锁定,超过 ±6 音分后解锁。
05Sound声音
Speaker measurement and EQ喇叭测量与 EQ
Static EQ reduces speaker distortion by more than half. Measurements use the card’s microphone.静态 EQ 将喇叭失真降低一半以上。测量使用卡片自带的麦克风。
Audio timing. Host peak and RMS levels are normal. The audio task takes at most 1.6 ms per 16 ms block, with no dropouts.音频时序。Host 测试的峰值和 RMS 电平正常。音频任务每个 16 ms block 最多耗时 1.6 ms,无音频中断。
Measurement method. Diagnostic firmware plays sines from 130 to 1,760 Hz at three levels, the reference tone and three chords. Microphone audio streams as base64 over USB serial: 16 kHz mono, about 43 KB/s. NumPy segments the capture and measures distortion over harmonics 2 to 8.测量方法。诊断固件播放 130 到 1,760 Hz 的正弦信号(三档电平)、标准音和三个和弦。麦克风音频以 base64 经 USB serial 传回:16 kHz 单声道,约 43 KB/s。NumPy 对录音分段,计算 2 到 8 次谐波的失真。
Speaker response. Output below 1 kHz is weak. Excess cone excursion causes distortion. Chords around C3 concentrate energy in this low-response band.喇叭频响。1 kHz 以下输出较弱,振膜位移过大会产生失真。C3 附近的和弦能量集中在这一低响应频段。
Output processing uses a static chain at about 2% CPU:输出处理采用静态处理链,CPU 占用约 2%:
- Volume音量
- 300 Hz high-pass300 Hz 高通
- −8 dB below 1.1 kHz1.1 kHz 以下 −8 dB
- Peak limiter峰值限幅
- Soft clip软削波
Chords sound an octave above the displayed keyboard, which starts at C3. The reference tone concentrates energy in harmonics 2 to 5. Their exact integer frequency ratios preserve perceived A4 through the missing-fundamental effect.和弦发声音高比屏幕键盘高一个八度,键盘显示仍从 C3 开始。标准音的能量集中在 2 到 5 次谐波。谐波频率严格为整数倍,通过 missing-fundamental effect 保持 A4 的音高感知。
Fixed-point IIR state and rounding定点 IIR 状态与取整
The fixed-point IIR retains fractional state and rounds toward zero. Truncated output residuals measure 21 and 17 LSB. The table compares three variants with identical coefficients:定点 IIR 保留状态小数位,并向零取整。输出截断时,残留测量值为 21 和 17 LSB。下表对比相同系数下的三种实现:
| Filter state滤波器状态 | After the input stops输入停止后 |
|---|---|
| No fractional bits不留小数位 | 6–31 LSB oscillation after 10 s10 s 后仍有 6–31 LSB 振荡 |
| 8 fractional bits, plain shift留 8 位小数,普通右移 | persistent −1 LSB residual持续残留 −1 LSB |
| 8 fractional bits, round toward zero留 8 位小数,向零取整 | exactly zero完全归零 |
Fractional state and rounding toward zero produce exact silence. The instant-attack limiter bounds the output at 25,628. A host test with twelve full-velocity organ notes measures a peak of exactly 25,628.保留状态小数位并向零取整后,静音输出精确为零。Limiter 瞬时起音,输出上限为 25,628。Host 测试叠加 12 个满力度风琴音,峰值精确为 25,628。
06Performance性能
Rendering performance渲染性能
Three agents optimise rendering in parallel. Byte comparisons and fuzzing check output equivalence.三个 agent 并行优化渲染。逐字节比对和 fuzz 测试检查输出等价性。
The OCTAVE_WALK test build uses the device screen and microphone: 15 s on home, then 20 s per tool, with tool names logged over serial. A Mac plays 440 Hz to keep pitch detection active. Device render time is about 1.5× the QEMU estimate.OCTAVE_WALK 测试构建使用真机屏幕和麦克风:主页运行 15 s,每个工具运行 20 s,工具名输出到 serial。Mac 播放 440 Hz,让音高检测持续运行。真机 render 耗时约为 QEMU 估算的 1.5 倍。
- memo. Single-colour text on a solid background is determined by its parameters. Text repeated across two frames is cached as coverage runs. Replay checks each background pixel. Any overlap uses normal text rendering.
- stamp. Fixed shapes are drawn once in white onto black, and coverage is read back from the green channel: ⌊63·a/32⌋ is different for each of the 33 levels, so it inverts exactly.
- fast_gfx. Circle edge coverage uses 33 precomputed squared-distance thresholds. Each pixel performs a lookup.
- memo。纯色背景上的单色文字由参数确定。同一段文字连续两帧出现时,将 coverage 存为游程。回放前逐像素检查背景;存在覆盖时使用常规文字渲染。
- stamp。固定图形在黑底上用白色绘制一次,从绿色通道还原 coverage。⌊63·a/32⌋ 在 33 个级别上各不相同,可精确反推。
- fast_gfx。圆形边缘 coverage 使用预先计算的 33 级距离平方阈值,每个像素只需查表。
Validation results. Pitch comparisons cover 14,700 single windows and 125,608 streamed windows with 0 mismatches. A 3,750-frame host tour and 900 home-scrolling frames are byte-identical. Memo on/off matches in all 8 modes. Fuzzing: 2 × 400k cases, 0 failures. Morphing polygon edges: up to 104 pixels per frame differ by 1 LSB.验证结果。音高对比覆盖 14,700 个单窗口和 125,608 个流式窗口,0 处差异。3,750 帧 host 巡检和 900 帧主页滚动逐字节一致。Memo 开关在全部 8 种模式下输出一致。Fuzz 测试 2 × 40 万例,0 个失败。变形多边形边缘每帧最多 104 个像素相差 1 LSB。
Device home frame rate: 34–40 → 49.7–50 fps. Additional heap use is about 55 KB.真机主页帧率:34–40 → 49.7–50 fps。额外 heap 占用约 55 KB。
07Process过程
Parallel agent developmentAgent 并行开发
Shared interfaces and file ownership define parallel agent work. I set requirements and evaluate visual and audio output on the device.通过共享接口和文件归属划分 agent 并行工作。我负责设定需求,并在真机上评估画面和音频输出。
10-09 07:4210-1010-1114:33
Timeline to scale. Parallel development took five and a half hours. The other two days covered sound, keys, Chinese, the start screen and device testing.时间线按比例绘制。并行开发耗时五个半小时,其余两天用于音频、按键、中文、首屏和真机测试。
Agent roles. One framework agent implements shared services, the host simulator, tests, a reference tuner and the developer guide. Four agents implement tools; three optimise performance. An independent tester uses a different model to build firmware, run tests and add coverage.Agent 分工。一个框架 agent 实现共享服务、host 模拟器、测试、作为参考实现的调音器和开发指南。四个 agent 开发工具,三个 agent 优化性能。独立测试使用另一模型,负责构建固件、运行测试和补充测试覆盖。
- File ownership. Tool agents edit their own tool files and tests. Framework requests are recorded for the framework agent to implement.
- One prefix. Every global symbol starts with
oc_. The host test links every project on the card into one binary, so a name clash fails the build. - Isolated builds. Each agent has its own build and preview directories.
- Development contract. The guide defines interfaces, key conventions, animation rules and performance constraints: integer-only per-pixel operations and no 64-bit arithmetic in the strip loop.
- 文件归属。工具 agent 只修改自己负责的工具文件和测试。框架需求以文档记录,由框架 agent 统一实现。
- 统一前缀。全局符号以
oc_开头。Host 测试将卡上全部作品链接到同一个 binary,符号冲突会导致构建失败。 - 构建隔离。每个 agent 用自己的构建和预览目录。
- 开发约定。指南定义接口、按键约定、动效规则和性能约束:逐像素运算只使用整数,strip 循环中不使用 64 位运算。
Input and luminance checks. Start-screen luminance change: 0.188 → 0.071, with a 0.10 limit. Home accepts only presses that begin on home. A 500 ms long-press returns to the start screen.输入与亮度检查。首屏亮度变化:0.188 → 0.071,阈值为 0.10。主页仅处理从主页开始的按键。长按 500 ms 返回首屏。
Project-defined per-frame flash checks cover every tool, home and settings. Average luminance change between adjacent frames is limited to 0.25, or 0.10 with reduced flashing enabled.项目定义的逐帧防闪烁检查覆盖每个工具、主页和设置。相邻帧平均亮度变化上限为 0.25,开启减弱闪烁后为 0.10。
Device review. The sustained-note tool’s home icon and the reference tone each received two rounds of visual or listening checks on the card.真机评估。长音工具的主页图标和标准音分别进行了两轮真机视觉检查或听音检查。
- Requirements defined需求确定
- Framework complete; four tool agents start框架完成,四个工具 agent 开始开发
- Device home measurement: 39.9 fps真机主页测量:39.9 fps
- Performance work merged: 49.7–50 fps性能优化合并:49.7–50 fps
- Chinese interface中文界面
- Speaker measurement and output processing喇叭测量与输出处理
- Key mapping and colour-coded hints按键映射与颜色提示
- Submitted to the FoloToy community提交到 FoloToy 社区
- Approved and public审核通过,公开
Sydney time, 2026.悉尼时间,2026 年。
08After release发布之后
FRET, device measurements, iPhone versionFRET、真机测量、iPhone 版
An eleventh tool for guitar and ukulele, per-tool measurements on the card with the speaker silent, and a native iPhone app checked event by event against the firmware.新增吉他和尤克里里的第 11 个工具;喇叭静音时在卡上逐个工具测量;原生 iPhone app 与固件逐事件对照。
Revision 2715, public since 13 October 2026. FRET is the eleventh tool. EAR plays its questions and examples one octave above the displayed keyboard, with a lowest note of G3.修订 2715,2026 年 10 月 13 日起公开。FRET 是第十一个工具。EAR 的题目和示范音比屏幕键盘高一个八度,最低音为 G3。
FRETFRET Live已上线

Chord shapes for 6-string guitar and 4-string ukulele: 12 roots × 15 chord types. Each shape shows finger numbers, barres, open and muted strings, the note on each string and its function in the chord. Shapes are chosen from hand-written open and movable templates. Playback alternates strum and arpeggio. Settings gain an instrument option; the settings list scrolls over eight rows.六弦吉他和四弦尤克里里的和弦指法:12 个根音 × 15 种和弦。每个指法标出手指编号、横按、空弦和静音弦、每根弦的音名及其在和弦中的功能。指法从手写的开放式和可移动模板中选取。播放在扫弦和琶音之间交替。设置里新增乐器选项,设置列表八行滚动。
The pluck shown on screen is aligned with the sound that is heard. Notes rejected by the synth are not highlighted. Inserting a tool remaps the saved last-tool index. Mutation tests that break the timing, the preview cleanup or the save migration each make the FRET tests fail. Firmware 885,840 B.屏幕上的拨弦与实际听到的声音对齐。被合成器拒绝的音不会高亮。插入新工具后,存档里的上次工具索引会重新映射。分别破坏时序、预览清理和存档迁移的 mutation 测试都会让 FRET 测试失败。固件 885,840 B。
On-device measurement真机测量
A build flag zeroes the PCM after synthesis, so every CPU and DSP path runs while the speaker stays silent. Each tool is opened in turn on the card.一个构建开关在合成之后把 PCM 清零:所有 CPU 和 DSP 路径照常运行,喇叭不出声。在卡上依次打开每个工具测量。
| Device measurement真机测量 | Result结果 |
|---|---|
| Frame rate, original ten tools · 12 Oct帧率,原有十个工具 · 10 月 12 日 | 38.5–58 fps |
| Frame rate, home帧率,主页 | 48–50 fps |
| Minimum free heap最低空闲堆 | 65.4 KB |
| Pitch detection per call, mean音高检测单次耗时,平均 | 0.3–1.9 ms |
| Pitch detection per call, max in TUNE音高检测单次耗时,TUNE 最大 | 3.5 ms |
| DRONE audio task, mean / peak on voice changeDRONE 音频任务,平均 / 换音色峰值 | 6–8% / 31% |
| Fixed PCM inputs, card vs host固定 PCM 输入,卡上与主机对比 | 9 of 9 bit-identical9 组逐位相同 |
| Flash gate at 60 / 50 / 25 fps, largest step60 / 50 / 25 fps 闪烁门槛,最大跳变 | 0.054 |
OCTAVE for iPhoneOCTAVE iPhone 版 In progress开发中

A native app with ten tools, redesigned for touch. Audio runs at the device’s native 44.1 or 48 kHz, and the screen follows the time each note is heard.原生 app,十个工具,按触屏重新设计交互。音频以设备原生的 44.1 或 48 kHz 运行,画面跟随每个音实际被听到的时间。
The six CHORD arrangements are compared with the original C firmware, compiled separately as an oracle: 72 loops and 5,664 events match. A key change takes effect at the next safe boundary; rapid requests are merged. DRONE’s random seed, pluck and schedule match the firmware byte for byte over 3 seeds and 120 strikes. 200 core tests and 272 app tests pass.CHORD 的六种编排与原版 C 固件对照,固件单独编译作为 oracle:72 个循环、5,664 个事件一致。换调在下一个安全边界生效,快速连续的请求合并处理。DRONE 的随机种子、拨弦和排程在 3 个种子、120 次拨弦上与固件逐字节一致。Core 200 项、App 272 项测试通过。
