简介:这是一款基于Haskell的MIDI转ABC表示法工具,面向需要对传统曲调进行数字乐谱转换的音乐爱好者和开发者。项目当前重点支持Scandi风格等节奏规整的单音旋律,可将MIDI文件转换为格式正确的ABC文件;后续阶段计划处理真实钢琴演奏的MIDI输入,通过启发式方法修正音符时值与条线偏差,为乐谱生成与识别提供了分段实现思路。资源包共41个文件,包含11个Haskell源码、17个MIDI示例文件、10个Shell脚本以及说明文档,整体仅43KB,结构紧凑,便于快速阅读和编译测试。项目演示了从MIDI解析到ABC输出的完整流程,并附有多首传统舞曲样本,适合对函数式编程与音乐信息处理感兴趣的读者研究。目前已有781人浏览学习。
1. 为什么传统音乐需要 MidiToAbc
1.1 MIDI 和 ABC 是两种“语言”
做过传统音乐采集、记谱或者乐器教学的朋友,应该都遇到过这种场景:从现场录了一段音频,想转成方便印刷和分享的简谱/五线谱,结果要么手动一个个音符扒谱扒到眼瞎,要么找到一段 MIDI 文件却不知道怎么变成能编辑的文本谱。这个时候,MidiToAbc 这种工具就能派上大用场。简单说,它做的事情就是把计算机、合成器、编曲软件里常见的 MIDI 文件,转成用纯文本表达的 ABC 表示法——一种用字母、数字和符号记录旋律的轻量记谱格式。MidiToAbc 解决了从“机器能认的 MIDI”到“人容易读写的 ABC”之间的最后一公里问题。
MIDI 本身不承载任何声音波形,它记录的只是演奏指令:哪个音符在什么时间按下、力度多大、持续多久。这跟 ABC 记谱法有本质上的相似之处,因为 ABC 也是符号化地记录音高、时值、拍号,而不是记录实际音频。所以从理论上讲,MIDI 转 ABC 是一个“符号到符号”的映射过程,不涉及音频识别,也不涉及复杂的机器学习,纯粹靠规则就能完成。这也是 MidiToAbc 这类工具通常体积小、运行快、离线可用的原因。
1.2 谁需要这种转换
如果你玩传统音乐、民乐、古典室内乐,或者经常在论坛上跟人分享曲谱,你多半见过 ABC 谱。它只有一个纯文本文件,几百字节就能存下一整首曲子,在任何设备上都能打开,甚至可以直接嵌在网页里用 JS 渲染成五线谱。相比导出 PDF 或图片,ABC 更适合做版本对比、片段复用和自动化处理。而 MIDI 文件是现代音乐制作里最通用的中间格式,键盘上弹一段旋律,保存成 MIDI,再丢给 MidiToAbc,马上就能得到可读的 ABC 谱子,这个工作流对我来说已经是顺手得不能再顺手的日常。
需要这类转换的群体很明确:一是只懂音乐不想碰底层的爱好者,他们手上有一堆别人给的 MIDI 文件,想快速变成自己能看懂的谱子;二是做软件开发的工程师,想在程序里批量处理乐谱,比如把一个在线曲库的 MIDI 全部转成 ABC 做数据集;三是做电子乐器 DIY 的玩家,从 ESP32、Arduino Mega 这类板子里输入 MIDI 信号,再输出 ABC 谱存成文档。甚至有人用在线照片转 MIDI 的工具把简谱照片变成 MIDI,再用 MidiToAbc 变成 ABC 做进一步编辑,相当于给传统曲谱打通了一条数字化的快速通道。
2. 转换的核心思路:从二进制事件到字母乐谱
2.1 MIDI 文件里到底装了什么
想要正确转换,先得明白 MIDI 文件的内在结构。标准 MIDI 文件(SMF)由两个 chunk 组成:一个是文件头 MThd,另一个或几个是音轨数据 MTrk。文件头里最关键的是 division,也就是一拍被分成了多少个 tick。常见的值是 480,也有 120、960,这个值直接决定了音符时值的精度。音轨数据里是一个一个的事件,每个事件都有一个 delta-time,表示距离上一个事件过去了多少个 tick。最核心的事件是 Note On 和 Note Off,分别代表按下和抬起一个键,音高用 0~127 的数字表示,中央 C(C4)在 MIDI 里通常是 60,往下到 C3 是 48,往上到 C5 是 72。
在很多人的想象里,MIDI 转 ABC 无非是读个音符然后换行写字母。真做起来远没那么简单,因为 MIDI 允许同一时刻多个音符发声,允许同一个音轨里混着多个通道的不同乐器,还允许音符在 tick 精度上出现“不齐整”的时间偏移。比如人工弹键盘时,同一个和弦的几个音往往不是绝对同时按下,有的早了几个 tick,有的晚了几个 tick。如果转换时不做量化处理,ABC 谱里就会冒出一堆乱七八糟的三十二分音符和休止符,谱面根本没法看。
2.2 ABC 表示法的关键规则
ABC 记谱法用字母 A 到 G 表示音名,大小写代表八度的变化。默认情况下,小写字母 c 是中央 C 下方的 C(或者说 C4),而大写 C 反而比小写 c 高一个八度。这个规则刚接触时容易懵,但习惯了就好。八度的升降用撇号和逗号表示:c' 表示高一个八度,c,, 表示低两个八度。时值则用数字 1、2、4、8、16 等跟在音名后面,比如 A2 表示二分音符 A,D4 表示四分音符 D,数字是几分音符就是几。如果不写数字,默认是四分音符。小节线用竖线 |,拍号写 M:4/4,调号写 K:C,这些字段放在曲子的开头。
更复杂一点的是和弦。ABC 用方括号 [CEG] 表示三音同时发声,也就是和弦。装饰音、连音线、三连音等也有对应的符号。MIDI 转 ABC 时,需要把纵向重叠的音符合成和弦,把横向先后出现的音符合成旋律,再根据各音符的 tick 差值推算出时值。听起来不复杂,但碰到被延音踏板拉长的音符、力度变化带来的假音符,以及未按要求关闭的 Note Off 事件时,规则稍不严谨就会出错。
2.3 音符映射逻辑与时值换算
MIDI 音高转 ABC 音名的公式是这样的:用 MIDI 音高数字减去 60,得到相对中央 C 的半音数。如果差值在 -2 到 11 之间,会落在小写 c 到大写 B 的范围内;超出这个区间,就用撇号或逗号调整八度。具体映射可以用查表法,也可以用模运算处理。举个例子,MIDI 67 是 G4,对应 ABC 里的 g;MIDI 74 是 D5,对应 d'。实际实现时,我更喜欢把 MIDI 数字转成类似科学音高记号,再转成 ABC 字符串,因为中间多一步更方便处理移调。
时值换算更核心。假设 PPQ(PPQ = Pulses Per Quarter Note,即每四分音符的 tick 数)是 480,一个音符从 Note On 到 Note Off 持续了 960 个 tick,那它就是一个二分音符,因为 960 ÷ 480 = 2 个四分音符长度。如果持续了 240 个 tick,就是半拍,对应八分音符,用数字 8 表示。ABC 里 4 是四分音符,8 是八分音符,16 是十六分音符,换算表几乎是线性的。不过现实里音符长度很少正好是整数倍,会有轻微的人为误差,所以转换工具需要提供量化参数,比如“把误差小于 30 tick 的音符吸附到最近的八分音符网格上”。这个参数在 MidiToAbc 里通常写成 -q 或 --quantize。
3. 实操:一次完整的 MidiToAbc 转换
3.1 准备输入文件
我平时用得最多的场景,是把 MuseScore、Logic Pro 导出的 MIDI 文件转成 ABC。先确保 MIDI 文件本身是干净的:如果是从复杂编曲工程里导出的,最好只保留一条旋律轨,或者至少把节奏轨、和弦伴奏轨分开存成不同的 MIDI 文件。因为 MidiToAbc 虽然支持多轨,但多轨转出来会默认生成多个声部,谱面会非常拥挤,反而不利于查看旋律。
如果手头只有一张简谱照片,可以先用在线简谱转 MIDI 工具把照片里的音符识别成 MIDI,再拖进 MidiToAbc。这类照片识别工具偶尔会有音高识别错误,建议转完 ABC 后对着原谱逐句校对。我之前处理过一首笛子曲的简谱照片,识别出来的 MIDI 里把高音 1 认成了低音 5,如果不查原稿,整套谱子的调就全偏了。
3.2 命令行转换与参数说明
MidiToAbc 的典型用法特别简单,打开终端输入:
miditoabc input.mid -o output.abc默认情况下,它会读入 input.mid 的所有音轨,把每一条音轨作为一个声部输出,声部名用 V:1、V:2 区分。如果只想取第一轨的旋律,可以加个轨道选择参数:
miditoabc input.mid -t 1 -o output.abc我最常用的参数是量化阈值和休止符处理。量化阈值默认是 30 tick,但你最好根据自己的 MIDI 文件 PPQ 调整。PPQ 是 480 时,8 分音符长度是 240 tick,一个 30 tick 的误差只有 1/8 拍,影响不大;但如果 PPQ 是 96,8 分音符只有 48 tick,30 tick 的量化阈值就会把本来正常的八分音符扭曲成十六分音符。所以每次转换前先确认文件头的 PPQ,或者用 MidiToAbc 自带的-i参数查看文件信息:
miditoabc -i input.mid它会输出文件包含多少个音轨、每个音轨的通道、PPQ、音符数量等信息,方便你决定怎么转。
3.3 校验 ABC 结果
转出来的 ABC 文件看起来像这样:
X:1 T:Untitled M:4/4 L:1/8 K:C | c'2 d' e' | g4 f2 e2 | d' c' a2- a g | c4 z2 |这里面有几个值得注意的点。M:4/4是拍号,如果 MidiToAbc 没有从 MIDI 文件里正确读出来,就可能是默认的 4/4,跟原曲不符,需要手动改正。L:1/8是最短音符时值,这是影响频率长度的一个重要参数,MidiToAbc 一般会自动选择,但也允许手动指定。谱子里出现了z2,这是 ABC 里的休止符,表示两拍的空缺。
想要快速验证转换结果对不对,我会把 ABC 文本直接粘贴到在线 abcjs 渲染器里,它会实时显示五线谱,还能播放试听。如果听着哪里不对,再看是音高、时值还是调号的问题。另一个更硬核的校验办法是用命令行工具 abc2xml 把 ABC 转回 MIDI,如果转回去的音乐跟原始 MIDI 几乎一致,那至少说明没有丢关键信息。
4. 常见问题与避坑指南
4.1 多轨 MIDI 混在一起怎么办
这是新手最容易踩的坑。MIDI 文件里一条音轨可能同时包含旋律和伴奏,甚至一条旋律被拆到左右手两个通道里。如果不加处理直接转,ABC 会变成两个声部在同一小节里上下叠加,读起来非常费劲。解决思路有两个:一是转之前用 DAW 或 MIDI 编辑器把人声轨、钢琴轨分离开,各存成一个文件;二是用 MidiToAbc 的通道过滤功能,比如-c 0只转换通道 0 的数据。如果你的目标是传统音乐的单旋律谱,建议直接过滤掉低音区和打击乐通道,只保留主旋律所在的高音区,效率会高很多。
另外,MIDI 的打击乐通常在通道 10(索引 9),这一通道里的音符对应的是各种打击乐器,如果你要的是旋律谱,务必把这一通道排除掉。我见过有人转出来一堆 b 和 d 的乱码音符,结果发现是踩镲的开闭事件,跟旋律一点关系都没有。
4.2 调号和拍号还原不准怎么办
MidiToAbc 的调号推断逻辑通常是统计音阶中出现率最高的音符,然后推测可能是哪个大调或小调。但这套逻辑对传统音乐有点水土不服。很多中国民歌是五声音阶,不带 Fa 和 Si,MIDI 文件里压根没有足够信息判断调号,所以工具会默认给 C 调。这种情况下,我建议先看原谱或听试听确定调号,然后在 ABC 文件里手动改K:字段。比如原曲是 G 调笛子曲,就把K:C改成K:G。
拍号也是类似问题。MIDI 文件里的拍号元事件放在第一个音轨的特定位置,如果原文件是 DAW 里随便录制的,可能没有写入拍号信息,导致 MidiToAbc 默认输出 4/4。所以转换完第一件事就是检查M:和K:两行,别让调号拍号这种基础信息错了带偏整首曲谱。
4.3 常见故障快查表
| 症状 | 可能原因 | 处理方法 |
|---|---|---|
| 输出全是超长音符,没有休止符 | 某些 MIDI 音符只发 Note On 没发 Note Off,或者工具没识别到 Note Off | 用 MIDI 编辑器修复文件,或调整工具对“悬挂音符”的处理策略 |
| 音符时值忽长忽短,谱面很难看 | 量化参数设置不合理 | 根据 PPQ 重新设置量化阈值,或开启自动量化 |
| 多轨同时响,谱面重叠混乱 | 没有分轨或过滤通道 | 使用 -t 选择音轨,或用 -c 过滤通道 |
| 转出来没有调号字段 | MIDI 文件缺少调号元事件 | 手动在 ABC 开头添加 K: 字段 |
| 和弦处只显示一个音符 | 工具简化了重叠音符 | 确认工具的和弦模式,或手动用 [ ] 补充和弦 |
4.4 休止符和延音线怎么处理
MIDI 没有休止符的概念,它只记录“此刻没有琴键按下”。MidiToAbc 把同一音轨里两个相邻音符之间的时间缝隙识别成休止符,这在原理上没问题,但遇到延音踏板时就会出现误判。踩下延音踏板后,钢琴音符的 Note Off 会被延迟,但人耳听到的其实还是同一个音符,这就会导致转换时出现大量不该有的休止符或长音符。处理办法是转换前在 MIDI 编辑器里把延音信息清除,或者用 MidiToAbc 自带的“忽略 controller 事件”参数。我一般直接删掉 CC64(延音踏板)事件再转,出来的谱面干净得多。
延音线在 ABC 里用-表示。如果一个音符跨小节并在下一个音符重复同一个音高,工具通常能自动识别成延音线。但如果中间有一个很小的 gap(比如 5 个 tick 的缝隙),它就可能被拆成两个音。这时候我会把量化阈值调大一点,让缝隙被忽略,再重新转换。
5. 从电脑到硬件:MidiToAbc 的扩展玩法
5.1 串口、蓝牙 MIDI 采集后直接转谱
最近玩 ESP32 和 Arduino 这类板子的朋友越来越多,大家常问一个问题:Arduino Mega 支持 USB MIDI 吗?实际上,Arduino Mega 本身没有原生 USB MIDI 能力,通常需要外接 USB Host Shield 或者用 ATmega16U2 做二次开发。不过 ESP32 倒是天生支持蓝牙 MIDI,可以接收蓝牙 MIDI 键盘的输入。如果你在弹 MIDI 键盘,数据通过蓝牙传到电脑上的 MidiToAbc,就能秒转成 ABC 谱,这对现场采风、即兴录音特别有用。
我自己试过把 ESP32 作为 USB MIDI 转串口的桥接设备,板子收到 MIDI 事件后通过串口发到 PC,PC 上跑一个监听脚本实时把事件喂给 MidiToAbc,弹完一段立刻就有 ABC 谱出来。这样做最大的好处是省略了把 MIDI 存成文件的步骤,适合需要快速记录灵感的人。要注意的是串口传输有延迟,但 MIDI 事件本身只有几十字节,实测下来 115200 波特率完全不会丢数据。
5.2 在线工具打辅助的完整工作流
网上常看到“在线简谱转成 MIDI”或“在线把简谱照片转成 MIDI”的工具,这些工具跟 MidiToAbc 配合使用,基本能拼出一条全自动的曲谱数字化流水线。我的常用流程是:手机拍简谱照片 → 在线识别成 MIDI → 用 MidiToAbc 转成 ABC → 在编辑器里修正调号和装饰音 → 导出成 PDF 或嵌入网页。这套流程对没有专业扒谱基础的人非常友好,唯一要留意的是照片识别的准确率参差不齐,五线谱还好一些,简谱里的高音点、低音点经常被漏掉,所以转完必须逐句核对。
如果你是开发音教类 App 的工程师,还可以把 MidiToAbc 编译成 WebAssembly 或作为后端服务,做成“上传 MIDI 返回 ABC 文本”的 API。整个转换逻辑不依赖任何重型框架,拆出来也就几百行代码,很适合塞进服务器。我认识一个做在线乐谱库的朋友,就是靠这个思路把几万份老旧 MIDI 资源全部转成了 ABC,体积小了不说,还能直接在网页端实时渲染,用户体验提升了好几个量级。
说到这,回到 MidiToAbc 本身。折腾过这么多轮之后,我最大的感受是,工具再自动化,最终还是要靠人判断音乐语境。调号、装饰音、声部层次这些信息,MIDI 文件里藏着很多,但怎么解读、怎么取舍,取决于你想得到一份“能看”的谱子还是一份“能练”的谱子。我自己的习惯是,先用默认参数转一版,然后拷到 abcjs 里播放对比,再手动修掉那些机器判断不出来的味道。一次转换可能花不了两分钟,但手工修正能看出你对这首歌的理解有多深。下次你从旧 MIDI 里翻出一首快要遗忘的旋律,不妨试试这个流程,或许能重新找回手写谱子时的亲切感。
本文还有配套的精品资源,点击获取