
flipperzero-ocarina在 Flipper Zero 上复刻《时之笛》五孔奥卡利那笛的完整源码解析【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper导读flipperzero-ocarina 是运行在 Flipper Zero 上的一个极简奥卡利那笛Ocarina of Time模拟器它把 Flipper Zero 的五个物理按键映射为五孔陶笛的五个音孔按下发声、松开止音并完全沿用 N64 版《塞尔达传说时之笛》的演奏逻辑OK 键替代 A 键。本文以 ocarina/README.md 为骨架结合仓库中 ocarina.c 与 application.fam 的完整实现从按键映射、音高表、事件循环、扬声器驱动到 FAP 打包逐层拆解读者读完后既能直接玩转这款应用也能把它当作理解 Flipper Zero GUI 音频 输入子系统的最小范例。一、应用概述一个会响的按键矩阵按照原文档的描述这是一个basic Ocarinaof Time——一个基础版的、致敬《时之笛》的奥卡利那笛。它不做任何复杂的乐理计算也没有旋律回放功能而是把五孔笛这个乐器的核心交互还原了出来按下对应方向的按键 → 持续发出该音孔的音高松开按键 → 立即停止发声五个音孔对应五个方向键 中央 OK 键操作方式与 N64 版《时之笛》完全相同其中OK 键替代了原版 A 按钮。在 ocarina.c 的顶部五个音高被直接写成宏常量构成了整支笛子的音阶基础按键方向含义N64 同款频率常量对应音名近似InputKeyUp上NOTE_UP 587.33fD5InputKeyLeft左NOTE_LEFT 493.88fB4InputKeyRight右NOTE_RIGHT 440.00fA4标准音InputKeyDown下NOTE_DOWN 349.23fF4InputKeyOkA 键中央NOTE_OK 293.66fD4说明频率数值直接取自源码宏定义音名标注基于标准十二平均律换算440 Hz 即 A4可作为演奏时的参考。二、按键与音高映射从输入事件到扬声器整个应用的乐器逻辑集中在主循环里的一个switch语句中ocarina.cif(event.type InputTypePress) { switch(event.key) { case InputKeyUp: furi_hal_speaker_start(NOTE_UP, volume); break; case InputKeyDown: furi_hal_speaker_start(NOTE_DOWN, volume); break; case InputKeyLeft: furi_hal_speaker_start(NOTE_LEFT, volume); break; case InputKeyRight: furi_hal_speaker_start(NOTE_RIGHT, volume); break; case InputKeyOk: furi_hal_speaker_start(NOTE_OK, volume); break; case InputKeyBack: processing false; // 按 Back 退出 break; default: break; } } else if(event.type InputTypeRelease) { furi_hal_speaker_stop(); // 任意键松开即止音 }这里有三个值得注意的设计细节按下InputTypePress触发发音松开InputTypeRelease统一止音。furi_hal_speaker_stop()被放在Release分支中因此同时按下多个键时最后一个松开会导致整体止音——这是单声道蜂鸣器驱动的自然结果也决定了它一个音一个音吹的演奏风格正好符合奥卡利那笛的单音吹奏特性。音量被硬编码为1.0focarina.c即满音量驱动内部扬声器未提供音量调节入口体现了basic定位。InputKeyBack被用来优雅退出它不发声而是把循环标志processing置为false随后进入清理流程见第五节。三、事件驱动架构Flipper Zero 应用的经典四件套虽然功能简单但 ocarina 的代码结构非常完整是 Flipper Zero 原生 GUI 应用的标准范式包含四个核心组成部分ocarina.ctypedef struct { FuriMutex* model_mutex; // 保护共享状态的互斥锁 FuriMessageQueue* event_queue; // 输入事件队列 ViewPort* view_port; // 屏幕绘制视口 Gui* gui; // GUI 服务句柄 } Ocarina;1. 事件队列FuriMessageQueue在ocarina_alloc()中创建了容量为8 条、每条sizeof(InputEvent)的队列ocarina.c。硬件输入回调input_callback只做一件事——把输入事件put进队列void input_callback(InputEvent* input, void* ctx) { Ocarina* ocarina ctx; furi_message_queue_put(ocarina-event_queue, input, FuriWaitForever); }而应用主线程通过furi_message_queue_get(..., 100)以100 tick 超时轮询出队ocarina.c。这种回调只入队、主循环处理的模型把按键处理放回到应用线程避免在中断上下文或 GUI 线程中做耗时操作是 FuriOS 上推荐的线程安全写法。2. 互斥锁FuriMutexmodel_mutex采用FuriMutexTypeNormal类型ocarina.c在主循环的每次迭代前后分别acquire/release同时保护draw_callback中的绘制访问。虽然本应用没有可变模型数据但它演示了渲染与逻辑共享状态时必须加锁的规范。3. 视口与 GUIViewPortGuiocarina_alloc()中完成了 GUI 挂载的完整流程instance-view_port view_port_alloc(); view_port_draw_callback_set(instance-view_port, draw_callback, instance); view_port_input_callback_set(instance-view_port, input_callback, instance); instance-gui furi_record_open(gui); gui_add_view_port(instance-gui, instance-view_port, GuiLayerFullscreen);通过furi_record_open(gui)获取系统 GUI 服务Flipper Zero 的服务注册表机制将视口注册到GuiLayerFullscreen全屏层因此应用启动后会覆盖整个 128×64 屏幕。4. 绘制回调draw_callback屏幕内容非常简单但精确对应了 Flipper Zero 的屏幕规格128×64canvas_draw_frame(canvas, 0, 0, 128, 64); // 全屏边框 canvas_draw_str(canvas, 50, 10, Ocarina); // 标题 canvas_draw_str(canvas, 30, 20, OK button for A); // 操作提示界面上直接印着OK button for A——这正是原文档强调的 N64 键位移植点用 Flipper Zero 的中央 OK 键替代 N64 手柄的 A 按钮。主循环每次迭代末尾调用view_port_update()触发重绘ocarina.c。四、音频输出furi_hal_speaker的按下-松开模型所有发声都基于 Flipper Zero 硬件抽象层的两个 APIfuri_hal_speaker_start(frequency, volume)以指定频率Hz和音量0.0–1.0启动内部扬声器furi_hal_speaker_stop()停止发声。ocarina 的持续音效果完全依赖按键按下状态按住即持续输出方波音调松开即停。这一实现与该仓库其他音频应用使用了完全相同的 HAL 接口——例如同目录下的 tuning_fork.c调音叉、music_beeper_worker.c蜂鸣器音乐、morse_code_worker.c莫尔斯码以及 speaker_hal.c音乐追踪器都基于furi_hal_speaker_start发声。可以推断furi_hal_speaker是这批老版官方固件应用的通用音频通路。需要注意的兼容性前提本目录位于Applications/Official/source-OLDER/属于旧版官方固件时期的应用源码归档。仓库 Applications/Official/ReadMe.md 明确提示部分使用音频的应用因固件音频访问模式变更而无法正常工作原文Some apps which use audio wont work due to a new pattern for audio access。因此在新版官方固件上编译运行本应用时建议先确认当前固件对furi_hal_speaker_start的可用性并优先考虑使用官方应用商店FAP Store中维护的版本。五、资源释放与退出流程ocarina_free的逆序清理ocarina_free()展示了 Flipper Zero 应用的资源管理规范ocarina.c与ocarina_alloc()严格逆序void ocarina_free(Ocarina* instance) { view_port_enabled_set(instance-view_port, false); // 1. 禁用视口 gui_remove_view_port(instance-gui, instance-view_port); // 2. 从 GUI 摘除视口 furi_record_close(gui); // 3. 关闭 gui 服务记录 view_port_free(instance-view_port); // 4. 释放视口内存 furi_message_queue_free(instance-event_queue); // 5. 释放事件队列 furi_mutex_free(instance-model_mutex); // 6. 释放互斥锁 furi_hal_speaker_stop(); // 7. 确保退出时扬声器静音 free(instance); // 8. 释放实例 }特别值得注意的是第 7 步退出前强制调用furi_hal_speaker_stop()。因为本应用的音频输出完全由按键按下状态驱动若用户在按住某个音孔的瞬间按下 Back 退出扬声器可能仍在发声这行代码确保应用无论以何种方式退出都不会留下幽灵持续音。这个细节对任何使用扬声器的应用都具有借鉴意义。六、FAP 构建配置application.fam逐项解读应用通过 FAPFlipper Application Package清单 application.fam 描述构建元数据全文件仅 13 行App( appidOcarina, nameOcarina, apptypeFlipperAppType.EXTERNAL, entry_pointocarina_app, cdefines[APP_OCARINA], requires[gui], stack_size1 * 1024, order30, fap_iconicons/music_10px.png, fap_categoryMusic, fap_icon_assetsicons, )各字段的实际影响appid/name应用唯一标识与显示名均为OcarinaapptypeFlipperAppType.EXTERNAL编译为独立 FAP 文件可放置于 SD 卡/apps目录运行无需刷入固件entry_pointocarina_app入口函数名对应源码中的int32_t ocarina_app(void* p)ocarina.crequires[gui]声明依赖 GUI 服务即运行时通过furi_record_open(gui)访问的服务stack_size1 * 1024应用线程栈仅 1 KB——由于本应用不做任何堆上的复杂数据处理1 KB 栈足以支撑主循环、事件处理与绘制调用order30应用在菜单中的排序权重fap_iconicons/music_10px.png应用图标10×10 像素的极简音符图标图标资产目录为同级的icons/fap_categoryMusic在应用菜单中被归类到Music音乐分组下。七、从源码到实操如何构建与运行由于source-OLDER是旧版源码归档官方固件现已改用 FAP 商店分发机制详见 Applications/Official/ReadMe.md因此推荐两条使用路径直接使用编译产物官方固件用户可前往 Flipper 官方应用商店lab.flipper.net/apps或 Flipper 手机应用内的应用目录搜索 Ocarina 类音乐应用直接安装 FAP 文件本地构建若环境固件兼容见第四节的音频访问注意事项可将本目录源码置于固件仓库的applications_user目录通过./fbt fap_Ocarina之类的 fbt 命令编译出 FAP再拷贝到 SD 卡/apps/Music/目录后从应用菜单启动。启动后屏幕中央显示 Ocarina 标题与 OK button for A 提示玩家即可像吹奏《时之笛》中的奥卡利那笛一样方向键对应四个音孔OK 键替代 A 键按住发音、松开止音、按 Back 退出。八、延伸思考从五音笛到旋律作为basic实现ocarina 仍有明确的扩展空间且每个方向都有仓库内现成的参考实现多音阶切换当前五个固定频率硬编码于宏定义可参考同作者的 tuning_fork.c 引入按键切换音阶/调式旋律回放将按键序列录制为乐谱并自动播放可复用 music_beeper_worker.c 的节拍与音符队列模式乐曲文件加载若希望从 SD 卡读取曲谱musictracker 中的 speaker_hal.c 展示了更精细的扬声器控制封装可视化反馈当前draw_callback仅绘制静态文字可参照仓库中其他游戏类应用为按下的音孔增加高亮动画增强吹奏的即时反馈。总结flipperzero-ocarina 虽然只有 118 行 C 代码却完整覆盖了 Flipper Zero 应用的五大核心主题GUI 视口渲染、输入事件队列、互斥锁同步、HAL 扬声器驱动、FAP 构建打包。它的价值远超一个能响的玩具——对想学习 Flipper Zero 应用开发的人来说这是一份麻雀虽小、五脏俱全的最小可运行示例而对玩家来说它把《时之笛》中最具标志性的交互之一原汁原味地搬到了口袋里的 Flipper Zero 上。按下、松开、聆听——就是这么简单。【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考