flipnoise:将 Renoise .xrns 工程转换为 Flipper Zero FMF 音乐文件的实战指南
【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper
在 Flipper Zero 的 Music Player 中,FMF(Flipper Music Format)文件是最常用的曲库格式之一。仓库中的Music_Player/flipnoise目录提供了一个名为flipnoise的 Python 小工具,它能把音序器 Renoise 的歌曲工程(.xrns)自动转换为 Flipper Zero 可播放的.fmf文件,省去手工把音符序列翻译成 FMF 文本的繁琐工作。读完本文,你将理解.xrns与.fmf两种格式的结构差异、flipnoise 的完整转换流程与参数处理逻辑,以及生成文件后如何投放到设备上播放。
一、flipnoise 的定位:连接 Renoise 与 Flipper Zero 的桥接工具
根据 Music_Player/flipnoise/ReadMe.md 的说明,flipnoise 的功能可以用一句话概括:
Converts Renoise .xrns files to Flipper Zero .fmf music files
它解决的核心问题是:Flipper Zero 的 Music Player 只能播放纯文本格式的 RTTTL(.txt)或 FMF 文件,而 Renoise 保存的是结构化的 XML 工程。手工把 Renoise 中的音符逐行翻译成 FMF 的Notes:行既费时又容易出错。flipnoise 自动完成「解包工程 → 解析 XML → 提取元数据与音符 → 按 FMF 规范落盘」的全过程,其官方示例输出见 Flipnoise.fmf——一首 BPM 高达 500 的高能 chiptune/keygen 风格曲目。
从 flipnoise.py 头部注释可以读出作者的设计意图:
- 读取 Renoise 文件中的歌曲名、艺术家名和 BPM,作为输出文件的元数据;
- 空行用上一个音符填充(Empty lines are filled in with the last note played);
- 设计目标是制作高 BPM(例如 500)的 chiptune/keygen 风格音乐;
- Flipper Zero只有一个声音声道,因此建议在 Renoise 工程中只使用一条轨道。
二、FMF 文件格式:flipnoise 的产物长什么样
在深入转换逻辑之前,先看产物。FMF 文件是一个纯文本文件,由若干「键值头」加一条音符序列构成。以仓库中真实存在的示例 Music_Player/flipnoise/Flipnoise.fmf 为例:
Filetype: Flipper Music Format Version: 0 BPM: 500 Duration: 16 Octave: 5 Notes: A4, C5, E5, A4, A5, A4, E5, C5, ...各字段的含义(与 flipnoise.py 中写入代码一一对应):
| 字段 | 示例值 | 来源 / 说明 |
|---|---|---|
Filetype | Flipper Music Format | 固定标识,告诉 Music Player 这是 FMF 文件 |
Version | 0 | 固定为 0 |
BPM | 500 | 取自 Renoise 工程的GlobalSongData/BeatsPerMin |
Duration | 16 | 输出时固定写死为 16 |
Octave | 5 | 输出时固定写死为 5(基准八度) |
Notes | A4, C5, E5, ... | 音符序列,逗号分隔,每个音符带八度编号 |
值得注意的是,FMF 的音符语法比 flipnoise 输出的更丰富——仓库中其他手工编写的 FMF 文件(例如 Music_Player/Original_Files/F/Faded.fmf)中还能看到时长前缀(如8F#、16C#)和休止符P(例如12D#6, 8P, 8F#)。flipnoise 只生成不带时长前缀的普通音符,这是它「高 BPM + 均匀步进」设计哲学的体现:每个音符等长,速度靠 BPM 拉满。
三、flipnoise.py 使用方法
flipnoise.py 是一个单文件 Python 脚本,无需第三方依赖(仅使用zipfile、xml.etree.ElementTree、shutil、uuid等标准库)。
使用方式在脚本中直接给出(flipnoise.py L49-L52):
Usage: flipnoise [filename.xrns]即把.xrns文件路径作为唯一命令行参数传入:
python flipnoise.py my_song.xrns运行前提与限制(源码中有明确检查):
- 文件必须存在,且必须是合法的 Renoise 工程,否则脚本以
invalid_file_exit退出并提示... is not a valid Renoise song; - 当前目录不能有
./flipnoise_tmp/目录——脚本会在当前工作目录下创建该临时目录解压工程,若已存在则直接报错退出(flipnoise.py L57-L60)。转换成功后该目录会被shutil.rmtree删除。
成功转换后,脚本在当前目录生成一个形如:
<Artist>-<SongName>-<uuid4>.fmf的文件(文件名拼接逻辑见 flipnoise.py L120-L129),并打印Saved <filename>。
四、转换流程源码解析
flipnoise 的处理链路在 flipnoise.py 的main()函数中完整体现,可分为四步。
4.1 解包 .xrns 工程
.xrns本质是一个 zip 压缩包。脚本先把它复制进临时目录,再解压(flipnoise.py L57-L67):
tmp_dir = "." + APP_NAME + "_tmp/" os.mkdir(tmp_dir) shutil.copy(filename, tmp_dir) with zipfile.ZipFile(tmp_dir + filename, 'r') as zip_handle: zip_handle.extractall(tmp_dir)之所以复制到临时目录再解压,是为了避免污染当前工作目录里的其他文件。
4.2 解析 Song.xml 并校验格式
解压后,脚本定位工程内的Song.xml(常量SONG_FILENAME = "Song.xml"),解析失败即判定为非法 Renoise 工程。随后校验根节点标签是否为RenoiseSong(常量RENOISE_MAGIC),不匹配同样报错退出(flipnoise.py L69-L79)。
4.3 提取元数据
脚本从GlobalSongData节点下读取四个字段(flipnoise.py L81-L91):
./GlobalSongData/SongName—— 歌曲名;./GlobalSongData/Artist—— 艺术家名;./GlobalSongData/BeatsPerMin—— BPM,直接写入 FMF 的BPM:行;./GlobalSongData/Octave—— 八度(脚本会打印它,但输出文件中Octave固定写为 5)。
4.4 逐 Pattern、逐 Line 提取音符并填充空行
这是脚本的核心算法(flipnoise.py L93-L116):
for pattern in renoise_root.findall("./PatternPool/Patterns/Pattern"): for line in pattern.findall("Tracks/PatternTrack/Lines/Line"): index = int(line.attrib.get("index")) index_delta = index - last_index if index_delta == 0: index_delta = 1 note = line.find("NoteColumns/NoteColumn/Note").text note = note.replace("-", "") for i in range(0, index_delta - 1): notes += note + ", " notes += note + ", "逐段解读:
- 遍历
PatternPool/Patterns下所有 Pattern,再遍历每个 Pattern 单轨(Tracks/PatternTrack)的所有行(Lines/Line); - Renoise 的 Line 元素带有
index属性表示该行在模式中的位置。脚本用相邻两行的index差值(index_delta)判断中间跳过了多少行; - 空行填充:如果
index_delta > 1,说明中间有空白行,脚本用当前音符重复填充这些空位(for i in range(0, index_delta - 1)),最后再加上当前音符本身——这正是文档头注释中「Empty lines are filled in with the last note played」的实现; - 静音标记处理:
note.replace("-", "")把 Renoise XML 中表示静音的空音符文本(-)替换为空串,填充时即产生「空音符」,从而在输出中呈现为连续逗号。从仓库示例 Flipnoise.fmf 的 Notes 行可以看到音符全部为普通音名(A4, C5, E5…),没有时长前缀,与该算法的输出特征一致。
最终把所有 Pattern 的音符拼接成一个以Notes:开头的长序列,rstrip(", ")去掉尾部多余分隔符。
五、设计取舍与使用建议
结合源码与注释,使用 flipnoise 制作曲目时需要注意以下工程取舍:
- 只有一条轨道。Flipper Zero 只有一个声音声道,Renoise 工程中若使用多条轨,脚本也只读取
PatternTrack(首个轨道)。作者明确建议「only make one track in your Renoise song」。 - 节奏是均匀步进。输出音符不带时长前缀,每个音符等长。因此适合 BPM 极高(如示例的 500)的 chiptune/keygen 音色轰炸型曲目,不适合需要复杂节奏长短音的旋律——那类需求应直接编写带
8、16、2、P等前缀的 FMF 文件,参考 Music_Player/Original_Files/F/Faded.fmf。 - 八度语义。Renoise 的八度偏移会被读取和打印,但输出固定为
Octave: 5;音符文本中的八度编号(如C5)则来自 Renoise 的记谱,直接原样写入。 - Duration 固定为 16,属于输出模板的一部分,与音符总长无关。
- 清理现场。运行结束后确认当前目录没有残留
flipnoise_tmp/,若中途中断需自行删除该目录再重跑(脚本本身不会替你先删已存在的目录)。
六、把生成的 .fmf 放到 Flipper Zero 上播放
转换完成后,把生成的.fmf文件通过 qFlipper 等工具写入设备的 SD 卡。根据 Music_Player/ReadMe.md 的说明:
- 如果当前固件看不到 Music Player 应用,可先在 SD 卡(ext)上创建
music_player文件夹并放入曲目,部分固件会因此启用该入口; - TXT 与 FMF 文件必须放在
music_player文件夹的根目录下才能被 Music Player 直接识别,子目录组织在多数固件上不生效。
仓库内其他可作为格式参照的音乐资源还包括:RTTTL 格式的 Music_Player/Theme_Songs 与 Music_Player/RTTTL_DUMP(RTTTL 采用d=4,o=5,b=200式的行内参数语法,例如 Theme - Flipper.txt),以及 Music_Player/Original_Files 中按首字母分目录的 FMF/RTTTL 混合曲库。
七、小结与延伸阅读
flipnoise 用一个约 140 行的单文件脚本,完整打通了「Renoise 工程 → 单声道 FMF」的转换链路:zip 解包.xrns、以RenoiseSong魔数校验、从GlobalSongData抽取歌名/艺术家/BPM、以index_delta空行填充算法展开音符序列,最后按Filetype/Version/BPM/Duration/Octave/Notes模板落盘为 UUID 命名的.fmf文件。对于想在 Flipper Zero 上快速产出高 BPM chiptune/keygen 曲目的开发者,它就是当前仓库中可直接复用的工具链;若需要更细腻的时长控制,则可以直接参考仓库内带时长前缀与休止符的 FMF 示例手工编写。
关键文件索引:
- Music_Player/flipnoise/ReadMe.md —— flipnoise 项目说明与示例输出链接
- Music_Player/flipnoise/flipnoise.py —— 转换脚本完整实现
- Music_Player/flipnoise/Flipnoise.fmf —— BPM 500 的转换产物示例
- Music_Player/ReadMe.md —— SD 卡
music_player目录与固件注意事项
【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考