【免费下载链接】muse-gadget-sdk
Open source SDK to build Muse gadgets
本文以 esp32/tools/muse/avatar_prompt.md 这份"给 Muse 的提示词规格书"为核心,讲解 Muse 语音助手固件头像系统的完整技术脉络:64x64 程序化像素渲染器的 API 契约、硬件性能硬约束、调色板与逐状态动画节拍规范,以及如何借助 tools/muse/avatar.py 把提示词变成一块可以烧录到 ESP32 板子上的自定义头像。读完本文,你可以独立读懂并修改渲染器的 API 与性能预算,也能复现"提示词 → C 文件 → 主机校验 → 固件烧录"的完整工作流。
这份文档是什么:头像渲染器的"合同"
avatar_prompt.md 不是一篇给人看的教程,而是一份可直接投喂给 AI 助手的生成式规格书:它要求 Muse(固件的云端语音助手)"替你自己画头像"——读取你在 Muse 中设置的头像形象(图片、描述、人设),然后重写components/muse/avatar/muse_pixel.c 这一程序化像素渲染器,让板子屏幕上的小角色变成你专属的形象。
配套的 AVATAR_RECIPE.md 说明了整体工作流:把 avatar_prompt.md 连同当前渲染器一起发给 Muse,Muse 按规格书回传一个完整的 C 文件,工具链在主机上编译、跑通全部动画、渲染 GIF 预览,最后构建固件并烧录。规格书本身则定义了三个层次的约束:输出格式(怎样才算一个合格的回复)、硬件与性能(代码必须跑在什么目标上)、内容(画面必须包含什么)。下面逐层拆解。
输出契约:一个可编译的 C 文件
规格书对回复形式的规定非常严格(见文档OUTPUT一节):
- 回复必须是一个完整的 C 文件,放在单个
```c围栏代码块中,代码块之后不许有任何内容; - 文件顶部保留
// Copyright (c) Meta Platforms, Inc. and affiliates.行,并在版权行下方加一段注释块描述所画的角色(名字、外形、颜色、性格)——这条注释是给人(以及工具)核对"画的到底是不是这个角色"用的; - 必须能在桌面用
cc -O2 -Wall无警告编译,并能在 ESP-IDF 的 GCC 下编译; - 只允许包含
<math.h>、<stdbool.h>、<stdint.h>、<stdlib.h>、<string.h>和"muse_pixel.h"; - 找不到头像时,只回复一行
NO AVATAR: <查过什么>,不输出代码。
主机侧的工具恰好逐条校验了这些约定。avatar.py 中的extract_c()会从回复里挑出包含全部四个 API 函数名且引用muse_pixel.h的最大围栏代码块;description()用正则提取版权行后的第一块注释作为"角色描述"打印出来;若回复以NO AVATAR开头则中止流程。此外host_check()(avatar.py)用cc -O1 -g -Wall -Werror把回复文件与 tools/muse/anim.c 一起编译,再挂上-fsanitize=address,undefined跑一遍全部动画——注释解释了原因:越界写在开发机上只会崩溃,在板子上则悄悄破坏内存,所以必须在主机上先暴露它。校验不过就把编译器输出(前 60 行)发回 Muse 要求修复,最多两轮(FIX_ROUNDS = 2)。
渲染器 API:muse_pixel.h 定义的四个函数
规格书API一节原样复述了 muse_pixel.h 的接口,并要求"一字不改地精确实现"。对照仓库中的头文件(esp32/components/muse/muse_pixel.h):
#define MUSE_PX_W 64 #define MUSE_PX_H 64 typedef struct { muse_mode_t mode; float t; /* seconds since boot */ float mode_t; /* seconds in current mode */ float level; /* 0..1 live audio level */ float happy; /* 0..1 pet reaction */ } muse_pose_t; uint32_t muse_pixel_accent(muse_mode_t mode); // 0xRRGGBB,头像周围 UI 用 void muse_pixel_render(const muse_pose_t *pose); // 画一帧到 64x64 网格 void muse_pixel_set_size(int px); // 放大目标尺寸,上限 512 void muse_pixel_scale(uint16_t *dst, int stride_px, int x0, int x1, int y0, int y1);几个值得注意的设计决策:
muse_mode_t的取值来自固件的共享状态。muse_state.h 定义了MUSE_MODE_BOOT到MUSE_MODE_OFF加MUSE_MODE_COUNT共 8 个模式,规格书要求逐一实现对应动画。pose是唯一的输入:t(开机秒数)、mode_t(当前模式内秒数)、level(0..1 的实时音量,监听时是麦克风电平、说话时是播放电平)、happy(抚摸反应,升到 1 后在约 1.6 s 内缓出)。渲染器不访问任何全局时钟,纯函数式地由 pose 驱动,这让它可以被 anim.c 这样的主机工具用假数据逐帧"回放"。muse_pixel_scale()按显示条带(strip)调用,把 64x64 网格放大后的屏幕像素块[x0..x1] x [y0..y1]以 RGB565 写入目标缓冲,stride_px行距。头文件注释点明了动机:小屏 RAM 极其有限,完整的全尺寸图像永远不该在 RAM 里存在。muse_pixel_accent()返回当前模式的强调色,供头像外围 UI(光环、边条)取色,保证角色和界面色调一致。
硬件与性能硬约束:整数定点,帧预算 10/40 ms
规格书HARDWARE与PERFORMANCE一节给出了两条渲染链路的目标参数,这也是整个规格书中最"硬核"的部分:
| 目标 | CPU | 帧率 | 帧周期 | 渲染预算 |
|---|---|---|---|---|
| ESP32-S3 | 240 MHz | 25 fps | 40 ms | 每帧 < 10 ms |
| ESP32-C6 | 160 MHz,无 FPU | 20 fps | 50 ms | 每帧 < 40 ms |
在此之上是一组强制规则:
- 逐像素运算必须用 Q12 整数定点(
ONE = 1 << 12)。逐像素循环内禁止 float、pow、sqrt、sin、cos——需要曲线(如|u|^2.7、|u|^3.6、sqrt)时,一次性建查找表(LUT)再查表。浮点只允许出现在"每帧一次"或"每部件一次"的层级。C6 没有 FPU,这条约束决定了它 50 ms 的帧周期里能塞多少工作。 - 着色循环限定在角色包围盒内,不扫全 64x64。
- 量级参考:每个被覆盖像素最多几十次整数运算。
- 只用静态内存:帧缓冲、同尺寸的部件遮罩、查找表,禁止
malloc。
帧预算之外的验证手段也来自仓库:anim.c 用与设备一致的DT 0.04f(40 ms,注释标注 "same as muse_ui")在主机上按设备帧率回放 8 段动画(boot、idle、listening、thinking、speaking、happy、off、error)并导出 PPM 帧;make_gifs.py 把帧合成 GIF(每段动画共用一张 256 色调色板,帧间隔 40 ms)。规格书还留了一条"设备侧量时"的兜底:UI 不逐帧打日志,如果画面太重,就在 C6 上用esp_timer_get_time()给muse_pixel_render计时,S3 应低于 10 ms、C6 低于 40 ms。
从默认渲染器继承的部分:帧缓冲、调色板与时间
规格书KEEP FROM THE ORIGINAL一节划定了"哪些代码必须原样保留",边界非常清楚:凡与"默认头像长什么样"无关的代码,都照搬。默认渲染器是 avatar/muse_pixel.c(约 1086 行),它的结构恰好对应规格书的每一条要求:
帧缓冲:uint8_t调色板索引的 64x64 网格,黑色背景(索引 0 是0x000000,圆屏的边框也是黑的)。
调色板:一组颜色角色枚举,上限 32 个条目。默认角色用了 29 个(含背景),可以在 avatar/muse_pixel.c 里逐个数出来:
enum { C_BG = 0, C_OUT, /* outline */ C_OUT2, /* soft outline where the hood tucks around the face */ C_BD, /* fur dark */ C_BM, /* fur mid */ C_BL, /* fur light */ C_BH, /* fur highlight */ C_RIM, /* state-tinted rim light */ C_SKIND, /* face panel shade */ ... C_G0, /* state glow ramp, bright ... */ C_G1, C_G2, C_G3, /* ... deep */ C_AURA1, C_AURA2, C_SPK, C_ACC, C_SHADOW, C_HEART, C_WHITE, C_COUNT, };角色本身的颜色集中在一张固定表里;逐模式方案表(glow ramp 四档 + accent)则用指数混合向当前模式过渡,系数1 - expf(-dt * 7)——默认实现的SCHEMES[MUSE_MODE_COUNT]表(avatar/muse_pixel.c)就是规格书PER-MODE SCHEMES那张色值表的 C 语言版本。每帧对每个条目预计算一份 RGB565 和一份 0.72 倍亮度的 "dim" 副本。
时间:跨帧状态(调色板混合进度、眨眼与注视计时器、sparkle 相位)放在static变量里;每帧的dt取pose->t的增量,钳制到 0..0.2 s(防止切后台/卡顿后的巨帧),首帧取 0.04 s。
muse_pixel_set_size/muse_pixel_scale原样复用:一张"屏幕像素 → 网格单元"的映射表(最多 512 项),最高位标记某个单元块的最后一颗像素;当单元格边长 ≥3 像素时,最后一颗像素改用 dim 调色板,形成隐约可见的像素网格质感;重复行直接memcpy。
外观技术:4x4 Bayer 有序抖动做明暗渐变与软边缘(默认实现的BAYER4表见 avatar/muse_pixel.c);轮廓用部件遮罩的 4 邻域测试画 1 px 硬描边,四肢与身体重叠处再补接缝线;光源固定左上;脚下是抖动的地面阴影;受光边缘加一层随状态着色的轮廓光(rim light);眼睛、嘴、爱心、感叹号等小元素用.#o风格的微型位图直接 stamp 上去。
构图:角色约 32-36 px 宽、46-48 px 高,水平居中于x = 32,脚底靠近y = 56.5,四周留出放光环、圆环和 sparkle 的空间。
动画节拍:每个模式必须做到什么
规格书ANIMATION BEATS一节是"验收清单",要求逐条实现(适配你自己的角色身体)。整理如下:
所有模式共有的生命感:
- 轻微呼吸:身体宽高 ±3% 起伏;
- 随机眨眼:每 2.2-5.2 s 一次,偶发双连眨(每次约 0.16 s);
- 视线漂移:每 1.2-3.6 s 随机一个新目标,用
1 - expf(-dt * 14)缓动过去; - sparkle(小星星)在身体前后环绕;
- 一圈随模式着色的柔光 aura(用抖动过渡)。
逐模式节拍:
| 模式 | 动画要求 |
|---|---|
| BOOT | 从压扁(squash)弹出,0.6 s 完成;约 0.9 s 时睁眼;sparkle 逐个出现 |
| IDLE | 缓慢上下浮动;手臂、翅膀或爪子摆动 |
| LISTENING | 眼睛睁大、嘴呈小 "o"、眉毛上扬,手抬到脸侧像捧耳;点状圆环向外扩,声波特效随level增大;视线固定向前 |
| THINKING | 眼睛向上下左右瞟,"hmm" 嘴型,一只爪子抵下巴,身体轻微倾斜,头顶旁三个思考点依次跳起,sparkle 变快 |
| SPEAKING | 嘴张开程度跟随level(并加一点抖动避免定格),身体随声音起伏,手臂比划、脚步挪动,圆环与声波,腮红加重 |
| ERROR | X 形眼、平嘴,前 0.6 s 快速左右摇晃,头旁 "!",红色方案;happy在 ERROR 中一律忽略 |
| OFF | 关机约 1.3 s:挥手告别、闭眼、辉光渐暗 |
happy > 0 | 任何非 ERROR 模式下被抚摸:跳跃、手臂上举抖动、^^ 形笑眼、大笑脸,两颗爱心上浮;必须在 64 px 下读得出"高兴" |
最后一条是像素画的关键纪律:表情来自 2-5 px 的形状,所以要夸张,脸部与身体之间要有强对比。
逐模式配色方案与身体建模
规格书PER-MODE SCHEMES给出每模式的四级 glow ramp(亮→深)与 accent("除非你的颜色与之冲突,否则保留"):
BOOT ffffff cfe0ff 8fa8ff 5a5fe0 accent a9c0ff IDLE f4e8ff c7a4ff 9a6bff 5b3fd9 accent a77dff LISTENING e8faff 8fdcff 3fa2ff 2a5bd7 accent 5cb8ff THINKING ffe6ff ff9cf0 d35bff 7a2bd9 accent e07bff SPEAKING eafff4 9ff5cf 3fd9a0 1f9a7a accent 6ff0bf ERROR ffd6d6 ff6b6b c7304a 6b1a3a accent ff5c5c OFF d8d4ff 8f86d9 5a4fb0 2e2870 accent 7c72d0这套方案表在默认渲染器中对应SCHEMES静态表,muse_pixel_accent(mode)与逐帧混合都从这里取数——换角色时改FIXED固定色表即可,方案混合逻辑保持不动。
BUILDING THE BODY一节则规定了建模方法论:不要手绘位图,而是沿用原始风格用解析部件拼装——
- 身体与头部用超椭圆(superellipse,可多个);
- 脸部面板是一个内嵌超椭圆;
- 四肢是旋转后的椭圆;
- 眼睛和嘴是 stamp 上去的位图。
这样部件天然可以随 pose 移动、压缩(squash)与跟随姿态;体表颜色由伪法线与光源做点积得到基础明暗,叠加 Bayer 抖动,再叠加一个按位置哈希的稳定纹理(毛发、羽毛、鳞片的颗粒感)——"稳定"是关键:纹理坐标取角色局部而非屏幕坐标,移动时才不会闪烁。
工具链:从提示词到烧录的完整流水线
规格书定义了"要生成什么",而 tools/muse/avatar.py 是"怎么生成并落地"的执行者(流程描述见 AVATAR_RECIPE.md)。把板子(S3 或 AIPI Lite)插好后,最简路径是:
cd esp32 python3 tools/muse/avatar.py它通过板子向你的 Muse 要头像,所以本机不需要 SDK token。事后改细节则用:
python3 tools/muse/avatar.py --edit "make the ears bigger and the eyes green"源码(avatar.py 的main()与make_avatar())把流程展开为五步:
- 找板子并查状态:在 USB 上找到板子,通过串口控制台发
>status查询;若板子没连 Wi-Fi 或连不到你的 Muse(未在 App 里配对且无设备 token),会停下并说明要补什么设置。 - 把规格书发给 Muse:
request()把 avatar_prompt.md 全文、加上当前渲染器(首次附默认头像源码,--edit时附你现有的文件并要求"只改这一处,整份文件发回来")作为一条打字消息经板子发出,流式打印回复进度(字符数、耗时、"Muse is working on it")。 - 落盘:完整回复存为
components/muse/avatar/last_reply.md,提取出的 C 文件存为 components/muse/avatar/muse_pixel.c,被替换的旧文件备份为muse_pixel.c.prev;工具打印文件开头的角色描述注释(Muse drew: ...)。 - 主机校验:以警告即错误的标准编译,再挂地址/未定义行为 sanitizer 跑遍全部动画;任何一步失败就把错误回传 Muse 要求修复,最多两轮;同时用 make_gifs.py 给每段动画渲染一个 GIF 到
components/muse/avatar/gifs/。 - 构建并烧录:经 tools/muse/board.sh 构建固件并烧到板子。
常用选项:--no-flash(构建完即停)、--port(多板时指定串口)、--reply FILE(跳过询问 Muse,直接用你手动保存的回复,回复可以是整段对话或仅 C 文件)。退出码语义:
| 状态码 | 含义 |
|---|---|
| 0 | 完成 |
| 1 | Muse 的文件没通过,或构建/烧录失败(消息指明是哪一个) |
| 2 | 没找到板子、板子不应答,或该板不支持 USB 聊天 |
| 3 | 板子没连 Wi-Fi 或没连到你的 Muse |
板子不应答通常是固件太旧(早于串口聊天功能),加--board s3(或--board s3-216、--board aipi、--board sticks3、--board stopwatch、--board cores3、--board watcher)会先烧一版能聊天的固件再继续。
C6 与手动流程:C6 没有 PSRAM,不支持 USB 聊天,此时规格书只能自己走:把 avatar_prompt.md 粘贴给 Muse 并附上 avatar/muse_pixel.c 作为起点;保存整个回复后用
python3 tools/muse/avatar.py --reply reply.md --board c6完成"保存 → 校验 → 构建 → 烧录";加--no-flash且不插板子时只做校验和构建。也可以直接把 C 文件存到components/muse/avatar/muse_pixel.c,再用tools/muse/board.sh build <board>正常构建——CMakeLists.txt 会file(GLOB)探测该路径,存在即替换默认头像并打印Custom avatar: components/muse/avatar/muse_pixel.c。
这个目录被 gitignore,自定义头像留在你自己的机器上;删掉文件即回到默认头像(AGENTS.md 也明确"不要提交components/muse/avatar/下的任何文件")。
结果验收:GIF 预览与设备侧计时
- 无板预览:
python3 tools/muse/make_gifs.py /tmp/avatar_gifs用真实渲染器画你的头像(--default画默认头像)。产出 boot、idle、listening、thinking、speaking、happy、off、error 八个 GIF——正好对应 anim.c 中ANIMS表的八段动画;每一段对照规格书的动画节拍检查。 - 局部修正:某个状态不对劲,用
avatar.py --edit附上 GIF 名字和修改点,Muse 会在它自己的文件上编辑而不是重画。 - 设备侧计时:UI 不逐帧打日志,画面偏重时就在 C6 上用
esp_timer_get_time()给muse_pixel_render计时,上限是 C6 40 ms、S3 10 ms。
与板子对话:chat.py 的控制台协议
AVATAR_RECIPE.md 还解释了板子 USB 控制台的聊天协议,这也是avatar.py的底层通道:
python3 tools/muse/chat.py "What does your avatar look like?" python3 tools/muse/chat.py --status # 板子状态,JSON控制台以>开头的行是命令:status打印板子状态;chat+=TEXT追加一行消息,chat=TEXT追加最后一行并发送(支持\n、\t、\\转义);chat.cancel丢弃进行中的消息或整轮对话。板子对每一行应答,并以@chat {...}JSON 行流式回传回复,其结构在 components/muse/muse_chat.h 的muse_hatch_text_turn处有定义。打字发出的回合不会被朗读,按 talk 键可取消。power打印电池状态为@power {...},power.reset重新计量,tools/muse/power.py 把它变成报告。
一个板级细节值得记录:SenseCAP Watcher 走 CH342 桥、以3结尾的串口聊天,且需要固件开启MUSE_CONSOLE_UART;该桥会整包丢字节,所以 tools/muse/chat.py 对它的写入按 64 字节分批,并先发一两个换行唤醒浅睡——这也是发提示词在 Watcher 上比其他板子多花几秒的原因。
小结
avatar_prompt.md 的价值在于把"给板子画一个会动的小角色"这件事压缩成一份可机器执行的契约:API 四函数、64x64 网格与 ≤32 色调色板、Q12 定点与静态内存的性能红线、逐模式动画节拍与配色方案表,加上"整份 C 文件、单代码块、可无警告编译"的输出纪律。配套的 avatar.py 流水线则把这份契约真正闭环——主机上-Werror+ ASan 校验、失败自动两轮返修、GIF 预览、固件构建与烧录,全部围绕 muse_pixel.h 这个稳定的渲染器接口展开。理解了这份规格书,你就同时掌握了 Muse 固件头像系统的输入端(如何提出角色)与输出端(代码如何在真实硬件的预算内跑起来)。
【免费下载链接】muse-gadget-sdk
Open source SDK to build Muse gadgets
相关推荐
Muse Gadget 自造智能硬件:基于 muse-gadget-sdk 的 ESP32 与 Linux 双 SDK 完整实战指南
Muse Gadget 自造智能硬件:基于 muse gadget sdk 的 ESP32 与 Linux 双 SDK 完整实战指南 Muse Gadgets
Mermaid.js自定义渲染器:扩展渲染引擎与自定义输出格式
Mermaid.js自定义渲染器:扩展渲染引擎与自定义输出格式 引言:为什么需要自定义渲染器? 在软件开发过程中,图表和可视化是沟通复杂系统架构、业务流程和数据
图表库前端数据可视化如何开发OpenUSD渲染Delegates:从零开始实现自定义Hydra渲染器
如何开发OpenUSD渲染Delegates:从零开始实现自定义Hydra渲染器 OpenUSD(Universal Scene Description)作为业
图形学3D渲染
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考