Flipper Zero 上的《时之笛》陶笛:flipperzero-ocarina 按键玩法与源码实现全解析
2026/9/15 10:25:23 网站建设 项目流程

Flipper Zero 上的《时之笛》陶笛:flipperzero-ocarina 按键玩法与源码实现全解析

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

flipperzero-ocarina 是运行在 Flipper Zero 上的一把“《时之笛》陶笛”,它复刻了 N64 经典游戏《塞尔达传说:时之笛》中主角 Link 吹奏陶笛(Ocarina)的核心玩法:通过方向键和确认键组合,在 Flipper Zero 内置蜂鸣器上吹奏出旋律。本文以仓库中的 关联文档 为骨架,结合 ocarina.c 与 application.fam 的完整源码,讲解按键映射、音高设计、事件循环与扬声器驱动原理,并给出构建安装与吹奏实战指南。

1. 项目是什么:Flipper Zero 版《时之笛》陶笛

原 README 对项目的定位只有一句话:A basic Ocarina (of Time) for the Flipper Zero。翻译过来就是:一把运行在 Flipper Zero 上的基础版《时之笛》陶笛。它属于该仓库 Applications/Official/source-OLDER 目录下 kyhwana 整理归档的应用源码之一。

从 application.fam 可以看到,它被归类为fap_category="Music"(音乐类),是一个通过 Flipper 应用包(FAP)机制分发的外部应用apptype=FlipperAppType.EXTERNAL)。也就是说,它不烧录进固件,而是编译成.fap文件拷贝到 SD 卡,从 Flipper Zero 的应用菜单中启动。

它不依赖键盘矩阵或外部硬件,只使用 Flipper Zero 机身自带的压电蜂鸣器(speaker)发声,配合 GUI 服务(requires=["gui"])在 128×64 单色屏幕上绘制简单界面,是一个极简但五脏俱全的嵌入式 GUI + 音频应用示例。

2. 按键映射:与 N64 版完全一致的操作逻辑

README 明确指出:Controls are the same as the N64 version of the Ocarina of Time, the Ok button takes the place of the A button——操作方式与 N64 版《时之笛》完全相同,只是用OK 键代替了 N64 手柄上的A 键

对应到 ocarina.c 的switch(event.key)分支,完整映射如下:

Flipper Zero 按键N64 对应触发音符频率 (Hz)音名(近似)
上 (Up)C-UpNOTE_UP587.33D5
左 (Left)C-LeftNOTE_LEFT493.88B4
右 (Right)C-RightNOTE_RIGHT440.00A4
下 (Down)C-DownNOTE_DOWN349.23F4
OK (Ok)A 键NOTE_OK293.66D4
返回 (Back)退出应用

在游戏原作中,玩家正是通过 C 键(方向键)与 A 键的组合吹奏不同音高,例如“时之笛”主题曲的经典起手式就是 C-Left、C-Right、C-Up。这个 Flipper 版本完整保留了这一交互设计,让熟悉原作的玩家可以无缝上手。

屏幕界面(见draw_callback,ocarina.c)会用canvas_draw_str显示标题 "Ocarina" 和提示文字 "OK button for A",并绘制一个 128×64 的全屏边框(canvas_draw_frame(canvas, 0, 0, 128, 64)),提示玩家 OK 键等价于 A 键。

3. 音高设计:五颗按键拼出的音阶

五个按键对应的频率在源码开头以宏定义:

#define NOTE_UP 587.33f // 上:D5 #define NOTE_LEFT 493.88f // 左:B4 #define NOTE_RIGHT 440.00f// 右:A4 #define NOTE_DOWN 349.23 // 下:F4 #define NOTE_OK 293.66f // OK:D4

从乐理上看,这五个音(D4、F4、A4、B4、D5)恰好覆盖了一个以 D 为根音的小调色彩音阶集合——其中 D4–F4–A4 构成 D 小三和弦,B4 与 D5 则向上延伸。这意味着:

  • OK(D4)作为低音主音,相当于旋律的“根音起点”;
  • 上(D5)与下(F4)构成五度/三度呼应,可以吹出常见的旋律骨架;
  • 原版游戏中最著名的“时之笛”主题曲(Zelda's Lullaby)以及“Epona 之歌”“Saria's Song”等曲目,其动机基本都能落在这几个音高附近,因此用这把“5 键陶笛”即可还原许多经典旋律片段。

音量方面,源码固定为float volume = 1.0f(满音量),由furi_hal_speaker_start(freq, volume)传入(见 ocarina.c)。

4. 源码级剖析:一个完整的 Flipper 事件驱动应用

虽然 README 只有两行,但 ocarina.c 是一个结构完整的 Flipper Zero 应用范例,其骨架(分配 → 事件循环 → 释放)可以作为任何简单 GUI 应用的模板。

4.1 应用结构体与初始化

typedef struct { FuriMutex* model_mutex; FuriMessageQueue* event_queue; ViewPort* view_port; Gui* gui; } Ocarina;

ocarina_alloc()依次完成四件事(ocarina.c):

  1. 互斥锁furi_mutex_alloc(FuriMutexTypeNormal):保护共享数据,供绘制回调与主循环并发访问时加锁;
  2. 消息队列furi_message_queue_alloc(8, sizeof(InputEvent)):容量 8 条的输入事件队列,作为 GUI 线程与主循环之间的生产者-消费者通道;
  3. ViewPortview_port_alloc():注册绘制回调draw_callback与输入回调input_callback
  4. GUI 服务furi_record_open("gui")打开 GUI 系统服务,再通过gui_add_view_port(..., GuiLayerFullscreen)把 ViewPort 挂到全屏图层

4.2 输入回调:事件进入队列

void input_callback(InputEvent* input, void* ctx) { Ocarina* ocarina = ctx; furi_message_queue_put(ocarina->event_queue, input, FuriWaitForever); }

输入回调在 GUI 线程上下文中执行,职责非常单一:把按键事件原样放入消息队列(FuriWaitForever表示若队列满则阻塞等待),不在此处直接驱动硬件,从而避免在中断/回调上下文中做耗时操作——这是 Flipper 应用开发的标准做法。

4.3 主循环:读队列、发音、刷屏

ocarina_app()是整个应用的入口(同时也是application.fam中声明的entry_point),它运行一个事件循环(ocarina.c):

for(bool processing = true; processing;) { FuriStatus status = furi_message_queue_get(ocarina->event_queue, &event, 100); furi_check(furi_mutex_acquire(ocarina->model_mutex, FuriWaitForever) == FuriStatusOk); ... furi_mutex_release(ocarina->model_mutex); view_port_update(ocarina->view_port); }

关键行为:

  • furi_message_queue_get(..., 100)100 tick 超时轮询队列:没有输入时循环也能周期性醒来并刷新屏幕;
  • 只有event.type == InputTypePress(按下瞬间)才启动音符,InputTypeRelease(松开)时调用furi_hal_speaker_stop()停止发声(ocarina.c)。按住不放会持续发声,松开即止,这与真实吹奏陶笛的“持续音-断音”控制一致;
  • InputKeyBack(返回键)将processing置为false,退出循环,随后调用ocarina_free()释放资源。

4.4 发声原理:furi_hal_speaker

声音输出使用 Flipper HAL 的furi_hal_speaker_start(frequency, volume)/furi_hal_speaker_stop()。该接口直接驱动机身内置压电蜂鸣器,以指定的频率(Hz)产生方波音调。这是 Flipper Zero 最简的音频输出路径——不需要外接扬声器,代码也不依赖任何音频缓冲队列。

需要留意一个细节:本仓库中同一应用的三个归档版本对扬声器资源的管理有差异:

  • kyhwana 版(本文关联文档对应版本,ocarina.c)直接调用furi_hal_speaker_start,并在释放时无条件furi_hal_speaker_stop()
  • xMasterX 版(ocarina.c)则引入了furi_hal_speaker_acquire(1000)/furi_hal_speaker_is_mine()/furi_hal_speaker_release()的资源占用检查,确保蜂鸣器未被其他应用占用时才发声,退出时也只在确认“扬声器归我所有”后才释放。

后者的写法在共享外设(speaker 可被闹钟、通知等多处使用)的场景下更安全,可以作为编写更健壮音频应用的参考——这也侧面说明在 Flipper 固件迭代中,外设资源管理经历了从“裸用”到“acquire/release 所有权模型”的演进。

4.5 资源释放

ocarina_free()(ocarina.c)按与分配相反的顺序清理:禁用并移除 ViewPort → 关闭 gui record → 释放 ViewPort → 释放消息队列 → 释放互斥锁 → 停止蜂鸣器 →free结构体。这套“对称分配/释放”的写法避免了资源泄漏,是嵌入式应用的生命周期管理范本。

5. 构建与安装:从源码到 SD 卡

应用清单 application.fam 完整声明了构建所需信息:

App( appid="Ocarina", name="Ocarina", apptype=FlipperAppType.EXTERNAL, entry_point="ocarina_app", cdefines=["APP_OCARINA"], requires=["gui"], stack_size=1 * 1024, order=30, fap_icon="icons/music_10px.png", fap_category="Music", fap_icon_assets="icons", )
  • apptype=FlipperAppType.EXTERNAL:编译为独立 FAP 应用(.fap),而非固件内置;
  • entry_point="ocarina_app":启动入口,对应 ocarina.c 中的int32_t ocarina_app(void* p)
  • requires=["gui"]:声明依赖 GUI 服务,构建系统会据此链接对应模块;
  • stack_size=1 * 1024:应用线程栈仅 1 KB,说明该应用占用资源极低;
  • fap_icon="icons/music_10px.png":菜单图标取自 icons/music_10px.png(10×10 像素的音乐符号位图);
  • fap_category="Music":应用在菜单中归类到“Music(音乐)”分组。

部署路径(基于 Flipper Zero 通用 FAP 流程,以当前仓库为源):

  1. 准备 Flipper Zero 官方固件 SDK 与fbt构建工具链;
  2. 将 kyhwana/ocarina 目录作为外部应用接入构建(或直接纳入applications外部应用目录);
  3. 执行fbt fap_ocarina(或通过fbt的 FAP 构建目标)生成ocarina.fap
  4. 将生成的.fap拷贝到 SD 卡的apps/Music/目录(图标资源一并放入);
  5. 在 Flipper Zero 上进入Apps → Music,选择 Ocarina 启动。

说明:上述构建命令以 Flipper Zero 官方fbt工具链为准,具体目标名以所用固件版本的构建系统输出为准;本仓库仅提供应用源码与清单,不包含固件工程。

6. 实战玩法:如何用它吹出旋律

把 README 的按键说明转化为实际演奏方法:

  • 单音吹奏:按下对应方向键(或 OK)即发声,松开即停。短促点按可形成断音(staccato),长按则得到持续音;
  • 连续旋律:用“按下-松开-按下-松开”的节奏依次触发不同音符。例如用 OK(D4)→ 上(D5)→ 右(A4)的走向即可构成类似《塞尔达摇篮曲》开头的上行动机;熟悉原作的玩家可以直接套用 N64 版的曲目按键谱(如 Zelda's Lullaby、Epona's Song、Saria's Song 的经典 C 键序列);
  • 退出:按返回键(Back)结束应用,蜂鸣器自动停止(见ocarina_free中的furi_hal_speaker_stop())。

得益于按键映射与原版一致、五个音高经过挑选,这把“口袋陶笛”既能用来复刻经典曲目,也可以自由即兴。

7. 延伸阅读:仓库内的其他实现与相关资源

  • 同款应用的其他归档版本:参见 grnch/ocarina 与 xMasterX/ocarina,其中 xMasterX 版补充了furi_hal_speaker_acquire资源保护,适合对照学习外设所有权管理;
  • 同类furi_hal_speaker用法:仓库中 tuning_fork.c、dtmf_dolphin_hal.c、music_beeper_worker.c 等同样是基于 HAL 蜂鸣器发声的应用,可对比不同音源驱动的写法;
  • 若想进一步了解 Flipper Zero 的音频/音乐能力,可浏览仓库的 Music_Player 目录(RTTTL 曲库与主题曲资源)作为曲目来源参考。

总结

flipperzero-ocarina 用不到 120 行 C 代码,在 Flipper Zero 上实现了一把致敬《塞尔达传说:时之笛》的 5 键陶笛。它既是 N64 经典操作的“口袋复刻”,也是一个结构清晰、可以直接学习的 Flipper Zero 事件驱动应用范例——从消息队列、ViewPort 回调、互斥锁,到furi_hal_speaker方波发声,覆盖了嵌入式 GUI + 音频应用的完整链路。读者既可以按上文流程构建安装,把它当成随身的小乐器把玩,也可以借这份源码掌握 Flipper Zero 外部应用开发的核心套路。

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询