跳到主要内容MIDI 全解析:从协议底层到 Python 实操,音乐开发与 AI 作曲 | 极客日志PythonAI算法
MIDI 全解析:从协议底层到 Python 实操,音乐开发与 AI 作曲
MIDI 协议底层原理、硬件连接及消息结构,涵盖二进制文件解析(Chunk、Delta-Time、VLQ)与 Python Mido 库实操(生成、编辑、硬件交互)。同时介绍 MIDI 2.0 新特性(16 位精度、双向通信)及开发工具集,适用于音频开发、AI 作曲及嵌入式设备控制场景。
追风少年6.2K 浏览 一、MIDI 基础:定义、起源与核心价值(开发前置认知)
1.1 精准定义:MIDI 不是音频,是'设备指令语言'
很多开发者初期会混淆 MIDI 与音频文件,核心认知需明确:MIDI 是通信协议,而非音频存储格式。
MIDI 的核心作用是定义电子乐器、计算机、效果器等设备之间的'控制指令传输规则',本质是设备间的通用通信语言。这些指令包含'音符启停、力度大小、乐器切换、音效调节'等所有音乐演奏相关参数,不存储任何声音波形。
1.1.1 MIDI 与传统音频文件的核心区别(开发必懂)
| 对比维度 | MIDI(.mid/.midi) | 音频文件(MP3/WAV/FLAC) |
|---|
| 存储内容 | 音乐控制指令(音符、力度、乐器、音效参数等) | 声音波形采样数据(直接记录声音本身) |
| 文件体积 | 极小(一首 3 分钟乐曲仅几 KB~几十 KB) | 较大(3 分钟 MP3 约 3MB,WAV 约 30MB) |
| 编辑灵活性 | 极高,可精准修改单个音符的参数(力度、时长、乐器),支持批量编辑 | 较低,仅能整体调节音量、均衡器,无法拆分单个音符 |
| 播放依赖 | 依赖音源(软音源/硬音源),由音源解析指令生成声音 | 直接播放波形,无需额外音源 |
| 开发场景 | AI 作曲、游戏配乐、音乐编程、设备控制 | 音频剪辑、语音识别、声音降噪 |
1.1.2 MIDI 的核心优势(为什么开发必学)
- 兼容性强:全球统一标准,不同厂商设备(Yamaha、Roland、Korg 等)均可互通
- 数据量小:便于网络传输、嵌入式设备存储,适合 AI 训练时减少数据量
- 可编辑性:指令化数据支持精准控制,适配个性化音乐生成需求
- 跨平台:支持 Windows、macOS、Linux 及嵌入式系统,适配多端开发场景
1.2 起源与发展:从'设备割据'到'全球标准'
MIDI 的诞生源于 1980 年代初电子乐器行业的'兼容性危机',其发展历程直接决定了当前的协议规范,开发时需了解核心时间节点对应的技术迭代。
1.2.1 起源背景(1981-1983 年)
1980 年代初,电子琴、合成器等设备快速普及,但不同厂商的设备采用各自的通信协议,导致'甲厂键盘创作的曲子,乙厂合成器无法正常播放'——音色错乱、节奏偏移、力度失效等问题频发,严重阻碍了行业发展。
1981 年,Sequential Circuits 公司的 Dave Smith 首次提出'统一电子乐器通信标准'的构想;1983 年,Yamaha、Roland、Korg、Sequential Circuits 等 13 家厂商联合召开会议,正式确立 MIDI 1.0 协议,免费开放给全行业使用,奠定了现代电子音乐与音频开发的基础。
1.2.2 核心发展时间线(开发兼容性参考)
- 1983 年:MIDI 1.0 协议发布,定义核心消息类型、硬件端口、传输速率(31250 bps),成为行业基础标准。
- 1984 年:Roland 发布 GS 标准,扩展音色库(从 128 种增至更多),增加鼓组定义、效果器参数,提升音乐表现力。
- 1991 年:GM(General MIDI)标准发布,由 MIDI 制造商协会(MMA)制定,统一 128 种音色的编号、鼓组通道(通道 10),实现'一次创作,全设备通用',是当前最基础的兼容标准。
- 1994 年:Yamaha 发布 XG 标准,在 GM 基础上扩展更多音色、控制器参数,支持多通道效果器,广泛应用于民用电子乐器。
- 2014 年:MMA 启动 MIDI 2.0 协议研发,解决 1.0 的带宽限制、单向传输等问题。
- 2020 年:MIDI 2.0 协议正式发布,支持双向通信、更高精度参数、动态音色映射,目前逐步普及,老设备仍兼容 1.0。
1.3 MIDI 的核心应用场景(开发方向参考)
MIDI 的应用已渗透多个技术领域,不同场景对应不同的开发重点:
- AI 作曲/深度学习:将 MIDI 作为训练数据格式,生成音符序列,实现自动作曲(如基于 Transformer 模型生成 MIDI 乐曲)。
- 游戏音效开发:通过 MIDI 实时生成游戏背景音,适配不同场景(战斗、探索、剧情),减少安装包体积。
- 电子音乐制作:通过 DAW(数字音频工作站)编辑 MIDI,搭配软音源制作专业音乐。
- 设备驱动开发:为 MIDI 键盘、合成器开发驱动,实现与电脑、手机的通信适配。
- 教育工具开发:开发乐理教学工具,通过 MIDI 实时反馈演奏者的音符准确性、力度控制。
二、MIDI 硬件与连接:端口、拓扑与适配方案(硬件交互重点)
开发 MIDI 相关硬件交互功能时,需掌握端口定义、连接拓扑及适配方案,避免兼容性问题。本节覆盖从传统硬件到现代 USB 适配的全场景。
2.1 MIDI 硬件核心端口定义(必懂)
传统 MIDI 设备均配备 3 个标准端口,分别对应不同的通信方向,硬件连接时需明确端口功能:
2.1.1 三大核心端口
- MIDI OUT(输出端口):用于发送 MIDI 消息到其他设备。例如,MIDI 键盘的 OUT 端口连接合成器的 IN 端口,键盘发送'音符指令'给合成器。
- MIDI IN(输入端口):用于接收其他设备发送的 MIDI 消息。例如,合成器通过 IN 端口接收键盘的指令,解析后发声。
- MIDI Thru(直通端口):用于转发 IN 端口接收的消息,不做任何修改。主要用于多设备串联,避免信号衰减。例如,3 台合成器串联时,键盘 OUT→合成器 1 IN,合成器 1 Thru→合成器 2 IN,合成器 2 Thru→合成器 3 IN,实现一台键盘控制多台设备。
2.1.2 端口物理接口
传统 MIDI 端口采用 5 针 DIN 接口(圆形接口),引脚定义如下(开发硬件驱动时需对应):
| 引脚编号 | 功能定义 | 电平标准 |
|---|
| 1 | 未使用(预留) | 无 |
| 2 | MIDI 信号(负逻辑) | 0-5V TTL 电平 |
| 3 | 接地(GND) | 无 |
| 4 | 未使用(预留) | 无 |
| 5 | +5V 电源(部分设备支持,非标准) | 5V DC |
注意:MIDI 信号采用负逻辑,高电平(5V)表示'0',低电平(0V)表示'1',传输速率固定为 31250 bps,无校验位,停止位 1 位,这是硬件通信的基础参数。
2.2 MIDI 设备连接拓扑(多设备交互方案)
实际开发中常涉及多设备连接,需掌握两种核心拓扑结构,适配不同场景:
2.2.1 串联拓扑(菊花链)
适用场景:少量设备(≤5 台),无同步需求,结构简单。
连接方式:设备 1 OUT → 设备 2 IN,设备 2 Thru → 设备 3 IN,依次串联,最后一台设备无需连接 Thru。
- 优点:无需额外设备,成本低,接线简单。
- 缺点:设备数量过多会导致信号衰减,延迟增加;单台设备故障会中断整个链路。
2.2.2 星型拓扑(通过 MIDI 接口盒)
适用场景:多设备(>5 台)、需要同步控制、高稳定性需求(如舞台演出、专业录音棚)。
连接方式:所有设备的 IN 端口连接到 MIDI 接口盒(或 MIDI 路由器)的 OUT 端口,接口盒作为中心节点,统一转发消息。
核心设备:MIDI 接口盒(支持多进多出,如 4 进 8 出、8 进 16 出),部分高端接口盒支持 USB 连接电脑,实现电脑对多设备的集中控制。
- 优点:信号稳定,无衰减;单台设备故障不影响其他设备;支持多设备同步控制。
- 缺点:需要额外购买接口盒,成本较高。
2.3 现代适配方案:USB-MIDI 与无线 MIDI
传统 5 针 DIN 接口逐渐被 USB 替代,无线 MIDI 也逐步普及,开发时需适配这些主流方案:
2.3.1 USB-MIDI(当前主流)
USB-MIDI 将 MIDI 消息封装在 USB 数据包中传输,兼容 USB 1.1/2.0/3.0,无需额外电源(部分设备除外),是当前电脑、手机与 MIDI 设备交互的主要方式。
- 即插即用:Windows、macOS、Linux 均自带 USB-MIDI 驱动,无需手动安装(部分小众设备除外)。
- 多通道支持:单根 USB 线可传输 16 个 MIDI 通道的消息,同时控制多台虚拟设备。
- 双向通信:支持设备向电脑反馈状态(如键盘按键状态、旋钮位置),这是传统 MIDI 1.0 的短板。
开发注意:USB-MIDI 设备在系统中被识别为'MIDI 设备'和'USB 设备'双身份,需通过系统 API(如 Windows 的 MMMIDI API、macOS 的 CoreMIDI)读取设备信息及消息。
2.3.2 无线 MIDI(新兴方案)
无线 MIDI 主要基于蓝牙(BLE MIDI)和 WiFi(MIDI over WiFi),适用于移动场景(如舞台演出、移动端音乐制作)。
- BLE MIDI:基于蓝牙低功耗技术,适配手机、平板等移动设备,延迟低(<10ms),功耗小,是民用场景的主流无线方案。
- WiFi MIDI:基于 UDP/TCP 传输,支持多设备远距离同步(<100 米),延迟略高于 BLE(10-20ms),适用于专业舞台场景。
开发注意:BLE MIDI 需遵循蓝牙 SIG 定义的 MIDI 服务规范(UUID:00001130-0000-1000-8000-00805F9B34FB),WiFi MIDI 需自定义消息封装格式,确保同步性。
2.4 常见硬件适配问题及解决方案(开发避坑)
实际开发中常遇到设备识别失败、消息丢失、延迟过高问题,对应解决方案如下:
| 问题现象 | 常见原因 | 解决方案 |
|---|
| 设备无法被系统识别 | 驱动未安装、USB 线故障、端口冲突 | 1. 安装设备官方驱动;2. 更换 USB 线及端口;3. 关闭占用端口的其他程序 |
| MIDI 消息丢失、卡顿 | 传输速率不足、设备供电不稳、链路过长 | 1. 减少串联设备数量,改用星型拓扑;2. 更换优质 USB 线/音频线;3. 为设备单独供电 |
| 播放延迟过高(>20ms) | 软音源缓冲设置过大、系统资源不足、无线干扰 | 1. 减小软音源缓冲(至 128ms 以下);2. 关闭后台冗余程序;3. 无线设备切换至 5G WiFi 或近距离 BLE |
| 多设备同步错乱 | 无统一时钟源、设备延迟不一致 | 1. 采用 MIDI 同步时钟(MIDI Clock);2. 通过接口盒统一管理设备时钟;3. 校准各设备延迟参数 |
三、MIDI 消息:二进制结构与核心类型(协议层核心)
MIDI 消息是设备间传输的核心数据,所有 MIDI 开发(文件解析、设备控制、AI 生成)都需基于消息结构实现。本节从二进制层面拆解消息格式,覆盖所有核心类型。
3.1 MIDI 消息的通用结构(二进制级解析)
MIDI 消息采用串行传输,以字节为单位,分为'状态字节'和'数据字节'两部分,部分消息还包含扩展数据。
3.1.1 字节分类与标识规则
- 状态字节(Status Byte):
- 最高位为 1(二进制:1xxxxxxx),十进制范围 128-255,用于标识消息类型及通道。
- 结构:高 4 位(bit7-bit4)为消息类型码,低 4 位(bit3-bit0)为通道号(0-15,对应 1-16 通道)。
- 示例:0x90(二进制 10010000)→ 高 4 位 0x9(Note On 消息),低 4 位 0x0(通道 1)。
- 数据字节(Data Byte):
- 最高位为 0(二进制:0xxxxxxx),十进制范围 0-127,用于存储消息的具体参数。
- 数量:根据消息类型不同,数据字节数为 1-2 个(部分系统消息可能更多)。
- 示例:Note On 消息的两个数据字节分别为'音高(0-127)'和'力度(0-127)'。
3.1.2 消息运行模式(Running Status)
为减少数据量,MIDI 支持'运行模式':连续发送同一类型、同一通道的消息时,仅第一个消息发送状态字节,后续消息可省略状态字节,直接发送数据字节。
示例:连续发送 3 个通道 1 的 Note On 消息,正常模式需发送 3 组(状态字节 +2 数据字节),运行模式仅发送 1 个状态字节 +6 个数据字节,大幅减少传输量。
开发注意:解析 MIDI 消息时需处理运行模式,记录上一个状态字节,直至遇到新的状态字节。
3.2 MIDI 消息分类(核心类型全解析)
MIDI 消息分为'通道消息'和'系统消息'两大类,通道消息对应单个通道的控制(如音符、音色),系统消息对应全局控制(如同步、启停)。
3.2.1 通道消息(Channel Messages)
通道消息共 7 类,均包含通道号(0-15),可独立控制 16 个通道的设备,是开发中最常用的消息类型。
(1)Note On(音符开启)- 状态字节 0x9n(n=0-15,通道号)
功能:通知设备开始播放某个音符,是最核心的消息之一。
| 字节位置 | 字节内容 | 取值范围 | 功能说明 |
|---|
| 1 | 状态字节 | 0x90-0x9F | 0x9n,n 为通道号(0-15) |
| 2 | 数据字节 1 | 0-127 | 音符音高(C4=60,对应中央 C,每半音递增 1) |
| 3 | 数据字节 2 | 0-127 | 演奏力度(0=无声,1-127=逐渐增强,64 为标准力度) |
特殊说明:当数据字节 2(力度)为 0 时,该消息等价于 Note Off 消息(部分设备支持此简化写法),但开发时建议单独发送 Note Off,避免兼容性问题。
(2)Note Off(音符关闭)- 状态字节 0x8n
功能:通知设备停止播放某个音符,必须与 Note On 配对使用,否则音符会持续发声。
| 字节位置 | 字节内容 | 取值范围 | 功能说明 |
|---|
| 1 | 状态字节 | 0x80-0x8F | 0x8n,n 为通道号(0-15) |
| 2 | 数据字节 1 | 0-127 | 音符音高(与对应的 Note On 一致) |
| 3 | 数据字节 2 | 0-127 | 释放力度(部分设备支持,控制音符衰减速度,0 为默认) |
(3)Polyphonic Pressure(复音触后)- 状态字节 0xA0
功能:控制单个音符的触后力度(按完琴键后持续施加的力度),实现音色变化,仅支持复音设备(可同时播放多个音符)。
| 字节位置 | 字节内容 | 取值范围 | 功能说明 |
|---|
| 1 | 状态字节 | 0xA0-0xAF | 0xAn,n 为通道号(0-15) |
| 2 | 数据字节 1 | 0-127 | 目标音符音高 |
| 3 | 数据字节 2 | 0-127 | 触后力度(0=无效果,127=效果最强) |
开发注意:多数民用设备不支持复音触后,仅高端合成器支持,可作为扩展功能实现。
(4)Control Change(控制变化)- 状态字节 0xBn
功能:控制设备的音效参数(音量、混响、颤音等),共支持 128 个控制器(CC0-CC127),是功能最丰富的通道消息。
| 字节位置 | 字节内容 | 取值范围 | 功能说明 |
|---|
| 1 | 状态字节 | 0xB0-0xBF | 0xBn,n 为通道号(0-15) |
| 2 | 数据字节 1 | 0-127 | 控制器编号(CC0-CC127,对应不同功能) |
| 3 | 数据字节 2 | 0-127 | 控制器参数值(0-127 对应功能的强弱/开关) |
核心控制器编号及功能(开发高频使用,完整列表见附录):
- CC1(调制轮):控制颤音、亮度,默认对应调制深度。
- CC7(主音量):控制对应通道的整体音量,0=静音,127=最大音量。
- CC10(声像):控制左右声道平衡,0=左声道,64=居中,127=右声道。
- CC11(表情强度):控制音量细节变化,比 CC7 更细腻。
- CC64(延音踏板):控制音符延音,0-63=踏板抬起,64-127=踏板按下。
- CC91(混响深度):控制混响效果强度,0=无混响,127=最强混响。
(5)Program Change(程序变化)- 状态字节 0xCn
功能:切换通道的乐器音色(如钢琴→小提琴),需配合 GM/GS/XG 音色表使用。
| 字节位置 | 字节内容 | 取值范围 | 功能说明 |
|---|
| 1 | 状态字节 | 0xC0-0xCF | 0xCn,n 为通道号(0-15) |
| 2 | 数据字节 1 | 0-127 | 音色编号(对应 GM/GS/XG 音色表,0=钢琴,16=小提琴等) |
特殊说明:通道 10(状态字节 0xCF)为 GM 标准规定的鼓组通道,Program Change 消息对其无效,鼓组音色通过 Note On 的音高控制(见附录鼓组表)。
(6)Channel Pressure(通道触后)- 状态字节 0xDn
功能:控制整个通道的触后力度,所有音符同时受影响,与复音触后(0xA0)的区别是不针对单个音符。
| 字节位置 | 字节内容 | 取值范围 | 功能说明 |
|---|
| 1 | 状态字节 | 0xD0-0xDF | 0xDn,n 为通道号(0-15) |
| 2 | 数据字节 1 | 0-127 | 触后力度(0=无效果,127=效果最强) |
(7)Pitch Bend(弯音)- 状态字节 0xEn
功能:控制音符音高的连续变化(滑音效果),默认范围为±2 个半音,可通过 CC 控制器扩展范围。
结构:1 个状态字节 + 2 个数据字节(14 位精度,高 7 位 + 低 7 位)
| 字节位置 | 字节内容 | 取值范围 | 功能说明 |
|---|
| 1 | 状态字节 | 0xE0-0xEF | 0xEn,n 为通道号(0-15) |
| 2 | 数据字节 1 | 0-127 | 弯音值低 7 位 |
| 3 | 数据字节 2 | 0-127 | 弯音值高 7 位 |
计算方式:弯音值 = 高 7 位 × 128 + 低 7 位,取值范围 0-16383。中间值 8192 为标准音高(无弯音),<8192 为降音,>8192 为升音。
示例:弯音值 8192+2048=10240 → 升 1 个半音;弯音值 8192-2048=6144 → 降 1 个半音。
3.2.2 系统消息(System Messages)
系统消息不包含通道号(状态字节高 4 位为 0xF),用于全局控制,如设备同步、启停、版权信息等,分为 3 子类:系统实时消息、系统通用消息、系统专属消息。
(1)系统实时消息(System Real-Time Messages)- 状态字节 0xF8-0xFF
功能:用于多设备同步(如播放、暂停、节拍同步),优先级最高,可插入其他消息中间传输,不影响正常播放。
| 状态字节 | 消息名称 | 数据字节 | 功能说明 |
|---|
| 0xF8 | MIDI Clock(时钟同步) | 无 | 每 24 个时钟消息对应 1 个四分音符,用于多设备节拍同步 |
| 0xF9 | MIDI Tick(节拍脉冲) | 无 | 每 4 个 Tick 对应 1 个 Clock,用于细分节拍(较少使用) |
| 0xFA | Start(开始播放) | 无 | 通知设备从当前位置开始播放 |
| 0xFB | Continue(继续播放) | 无 | 通知设备从暂停位置继续播放 |
| 0xFC | Stop(停止播放) | 无 | 通知设备停止播放 |
| 0xFE | Active Sensing(活性检测) | 无 | 设备定期发送,检测链路是否正常,超时无响应则停止发声 |
| 0xFF | System Reset(系统重置) | 无 | 重置设备至初始状态(音色、音量、控制器均恢复默认) |
(2)系统通用消息(System Common Messages)- 状态字节 0xF0-0xF7
功能:用于全局设置(如调号、拍号、速度)、文件传输等,部分消息包含扩展数据。
| 状态字节 | 消息名称 | 数据字节 | 功能说明 |
|---|
| 0xF0 | System Exclusive(系统专属) | 可变长度 | 厂商自定义消息,用于设备参数调节、固件升级(如 Roland、Yamaha 专属指令) |
| 0xF1 | MIDI Time Code Quarter Frame(时间码) | 1 个 | 传输 SMPTE 时间码,用于音视频同步 |
| 0xF2 | Song Position Pointer(歌曲位置指针) | 2 个 | 设置播放位置,以 MIDI Clock 为单位(0-16383) |
| 0xF3 | Song Select(歌曲选择) | 1 个 | 选择设备中的预设歌曲(0-127) |
| 0xF6 | Tune Request(调音请求) | 无 | 通知设备自动调音(仅部分硬件支持) |
| 0xF7 | End of Exclusive(专属消息结束) | 无 | 标记系统专属消息结束 |
(3)系统专属消息(System Exclusive Messages)- 状态字节 0xF0-0xF7
系统专属消息(SysEx)是厂商自定义的消息,用于控制设备的特殊功能(如参数调节、固件升级、音色备份),结构灵活,不同厂商格式不同。
通用结构:0xF0(开始) + 厂商 ID(1-3 字节) + 自定义数据(可变长度) + 0xF7(结束)
厂商 ID:由 MMA 分配,如 Yamaha(0x43)、Roland(0x41)、Korg(0x42),未分配厂商使用 0x7D。
开发注意:SysEx 消息兼容性差,仅针对特定设备,一般用于硬件驱动开发,AI 作曲、文件解析场景可忽略。
四、MIDI 文件格式:Chunk 结构、Delta-Time 与事件解析(文件处理核心)
MIDI 文件(.mid)是 MIDI 消息的持久化存储格式,所有文件解析、生成开发都需基于其结构实现。本节从二进制层面拆解文件结构,覆盖 Chunk、Delta-Time、事件解析全流程。
4.1 MIDI 文件的整体结构
MIDI 文件采用'Chunk(块)'式存储,每个 Chunk 包含'类型标识、长度、数据'三部分,整体由 1 个 Header Chunk(头部块)和 1 个及以上 Track Chunk(音轨块)组成。
MIDI 文件 → Header Chunk(头部信息) + Track Chunk 1(音轨 1) + Track Chunk 2(音轨 2) + ... + Track Chunk N(音轨 N)
核心规则:所有多字节数据采用'大端序'存储(高位字节在前,低位字节在后),开发解析时需注意字节序转换(如 C++ 的 htons 函数、Python 的 struct 模块)。
Header Chunk 是 MIDI 文件的'总说明书',存储文件格式、音轨数量、时间基准等全局信息,位于文件最开头,长度固定为 6 字节(不含类型和长度标识)。
Header Chunk 完整结构包含'类型标识(4 字节)+ 长度(4 字节)+ 数据(6 字节)'三部分,总长度为 14 字节,二进制层面逐字节解析如下,开发解析时需严格按此顺序读取:
字节偏移 | 字节长度 | 字段名称 | 取值范围/格式 | 功能说明 | 开发注意事项 |
0-3 | 4 字节 | Chunk 类型标识 | ASCII 码'MThd'(十六进制:0x4D546864) | 标识当前 Chunk 为 Header Chunk,是 MIDI 文件的起始标识 | 解析时先验证此标识,不匹配则为非法 MIDI 文件 |
4-7 | 4 字节 | Chunk 长度 | 固定值 6(十六进制:0x00000006) | 表示后续 Header 数据部分的长度为 6 字节 | 大端序存储,需转换为十进制后验证是否为 6 |
8-9 | 2 字节 | 文件格式类型 | 0、1、2(大端序) | 定义 MIDI 文件的音轨组织方式,对应三种格式: 1. 格式 0:仅 1 个 Track Chunk,所有 MIDI 消息混合存储; 2. 格式 1:多个 Track Chunk,同步播放(如旋律轨、鼓轨分离); 3. 格式 2:多个 Track Chunk,独立播放(较少使用,适用于多首独立乐曲) | 主流 MIDI 文件以格式 1 为主,格式 0 次之,需适配前两种格式 |
10-11 | 2 字节 | 音轨数量(NumTracks) | 1-65535(大端序,十进制) | 表示当前 MIDI 文件包含的 Track Chunk 数量 | 解析时需先读取此值,再循环读取对应数量的 Track Chunk |
12-13 | 2 字节 | 时间基准(Division) | 两种格式(大端序): 1. 高位 bit 为 0:值为每四分音符的 ticks 数(0-32767); 2. 高位 bit 为 1:值为负,低 15 位表示每帧的 ticks 数,帧速率固定(如 24、25、30 帧/秒) | 定义 MIDI 消息的时间精度,是节奏同步的核心参数: 例 1:Division=96 → 每四分音符对应 96 个 ticks,节奏精度为 96PPQ; 例 2:Division=0x8008(二进制 1000000000001000)→ 高位 bit=1,低 15 位=8,帧速率 30 帧/秒(默认) | 绝大多数民用 MIDI 文件采用第一种格式(PPQ),第二种(SMPTE)用于音视频同步 |
4.2.2 时间基准(Division)深度解析(开发核心)
时间基准直接决定 MIDI 消息的时间戳计算方式,是解析节奏、时长的关键,需区分两种格式的具体逻辑:
(1)PPQ 格式(高位 bit=0,最常用)
Division 值为正数(0-32767),表示'每四分音符对应的 ticks 数',记为 PPQ(Pulses Per Quarter Note)。ticks 是 MIDI 文件内部的时间单位,所有事件的时间戳(Delta-Time)均以 ticks 为单位。
-
四分音符时长(秒)= 60 / BPM(每分钟节拍数);
-
每个 tick 时长(秒)= 四分音符时长 / PPQ;
-
事件实际时长(秒)= Delta-Time × 每个 tick 时长。
示例:若 Division=96(PPQ=96),BPM=120(每分钟 120 拍),则:
四分音符时长=60/120=0.5 秒;每个 tick 时长=0.5/96≈0.0052 秒;Delta-Time=48 的事件,实际时长=48×0.0052≈0.25 秒(即八分音符)。
(2)SMPTE 格式(高位 bit=1,音视频同步用)
Division 值为负数(0x8000-0xFFFF),此时需将值拆分为两部分:
- 高位 1bit:固定为 1(标识 SMPTE 格式);
- 低 15bit:拆分为前 3bit(帧速率标识)和后 12bit(每帧 ticks 数)。
| 前 3bit 取值 | 帧速率(帧/秒) | 适用场景 |
|---|
| 000 | 24 | 电影标准 |
| 001 | 25 | PAL 电视标准 |
| 010 | 30(非丢帧) | NTSC 电视标准 |
| 011 | 30(丢帧,实际 29.97) | NTSC 广播标准 |
示例:Division=0x8008(二进制 1000000000001000),低 15bit=0x0008,前 3bit=000 → 帧速率 24 帧/秒,每帧 8 个 ticks,每个 tick 时长=1/24/8≈0.0052 秒。
基于 Python 的 struct 模块解析 Header Chunk,处理大端序转换,适配两种格式的时间基准,可直接嵌入项目:
import struct
def parse_header_chunk(file):
"""
解析 MIDI 文件的 Header Chunk
:param file: 打开的 MIDI 文件对象(二进制读模式)
:return: header_info: 字典,包含 Header Chunk 所有信息
"""
chunk_id = file.read(4)
if chunk_id != b'MThd':
raise ValueError("非法 MIDI 文件:缺少 MThd 标识")
chunk_length = struct.unpack('>I', file.read(4))[0]
if chunk_length != 6:
raise ValueError(f"Header Chunk 长度异常:预期 6 字节,实际{chunk_length}字节")
format_type, num_tracks, division = struct.unpack('>HHH', file.read(6))
division_info = {}
if division & 0x8000:
division_info['type'] = 'SMPTE'
frame_rate_flag = (division & 0x7000) >> 12
ticks_per_frame = division & 0x0FFF
frame_rate_map = {0: 24, 1: 25, 2: 30, 3: 29.97}
division_info['frame_rate'] = frame_rate_map.get(frame_rate_flag, 24)
division_info['ticks_per_frame'] = ticks_per_frame
else:
division_info['type'] = 'PPQ'
division_info['ppq'] = division
header_info = {
'format_type': format_type,
'num_tracks': num_tracks,
'division': division,
'division_info': division_info
}
return header_info
if __name__ == "__main__":
with open("test.mid", "rb") as f:
header = parse_header_chunk(f)
print("Header Chunk 解析结果:")
print(header)
代码说明:通过 struct.unpack 的'>'符号指定大端序,验证关键标识和长度,拆分时间基准格式,返回结构化信息,便于后续 Track Chunk 解析调用。
4.3 Track Chunk(音轨块)解析
Track Chunk 是 MIDI 文件的核心数据载体,每个音轨对应一条独立的演奏序列(如旋律轨、鼓轨、伴奏轨),文件中音轨数量由 Header Chunk 的 NumTracks 字段定义。所有音轨按时间同步播放,共同构成完整乐曲。
Track Chunk 结构与 Header Chunk 一致,均遵循'类型标识 + 长度 + 数据'的 Chunk 通用规范,但数据部分为可变长度(由 Chunk 长度字段指定),且包含大量带时间戳的 MIDI 事件。
4.3.1 Track Chunk 整体结构(二进制)
Track Chunk 总长度 = 4 字节(类型标识)+ 4 字节(长度)+ N 字节(数据,N 为 Chunk 长度字段值),二进制逐字段解析如下:
字节偏移 | 字节长度 | 字段名称 | 取值范围/格式 | 功能说明 | 开发注意事项 |
0-3(相对于 Track Chunk 起始) | 4 字节 | Chunk 类型标识 | ASCII 码'MTrk'(十六进制:0x4D54726B) | 标识当前 Chunk 为 Track Chunk,区分于 Header Chunk | 解析时需循环验证此标识,确保音轨块格式合法 |
4-7 | 4 字节 | Chunk 长度 | 0-4294967295(大端序,十进制) | 表示后续 Track 数据部分的总字节数,即音轨事件的总长度 | 需按此长度读取完整音轨数据,避免读取越界或遗漏事件 |
8-N+7 | N 字节(Chunk 长度值) | 音轨数据(事件序列) | Delta-Time + MIDI 事件 重复序列,以 0xFF 0x2F 0x00 结尾 | 存储音轨的所有演奏事件,含音符、控制器、节拍等信息 | 必须解析至结束事件(0xFF 0x2F 0x00),确保事件完整性 |
核心规则:所有 Track Chunk 的事件均以'Delta-Time + 事件内容'为单位存储,Delta-Time 表示当前事件与上一事件的时间间隔(单位:ticks),需结合 Header Chunk 的 Division 字段换算为实际时长。
4.3.2 Delta-Time 解析(时间戳核心)
Delta-Time 是 MIDI 文件时间同步的核心,采用'可变长度量(Variable-Length Quantity, VLQ)'存储,目的是减少时间戳的数据量(短间隔用少字节表示,长间隔用多字节表示)。
(1)VLQ 编码规则
VLQ 由 1-4 字节组成,每个字节的最高位(bit7)为'延续位',低 7 位为有效数据,具体规则:
- 若字节最高位为 1(二进制 1xxxxxxx):表示后续还有字节属于当前 VLQ 值,需继续读取下一字节并拼接低 7 位;
- 若字节最高位为 0(二进制 0xxxxxxx):表示当前为 VLQ 的最后一字节,低 7 位为最后一段有效数据;
- VLQ 最大值:4 字节(0x7F 0x7F 0x7F 0x7F),对应十进制 8388607,足够覆盖绝大多数 MIDI 乐曲的时间间隔。
(2)VLQ 解码流程与示例
解码时按字节读取,剥离延续位后拼接有效数据,计算方式:当前字节低 7 位 + 前一字节低 7 位×128 + 前两字节低 7 位×128² + ... ,示例如下:
| VLQ 字节序列(十六进制) | 解码过程 | 解码结果(十进制 ticks) |
|---|
| 0x40 | 单字节,延续位 0,有效数据 0x40(64) | 64 |
| 0x81 0x01 | 第一字节延续位 1(0x81→低 7 位 0x01),第二字节延续位 0(0x01→低 7 位 0x01),结果=0x01 + 0x01×128=129 | 129 |
| 0x8F 0x7F | 0x8F→低 7 位 0x0F,0x7F→低 7 位 0x7F,结果=0x0F + 0x7F×128=0x0F+0x3F80=0x3F8F=16271 | 16271 |
(3)Python 解码 VLQ 代码实现
def decode_vlq(file):
"""
解码 Delta-Time 的 VLQ 格式,读取可变长度字节并返回对应的 ticks 值
:param file: 打开的 MIDI 文件对象(二进制读模式)
:return: vlq_value: 解码后的 ticks 值(十进制)
"""
vlq_value = 0
while True:
byte = ord(file.read(1))
vlq_value = (vlq_value << 7) | (byte & 0x7F)
if not (byte & 0x80):
break
if vlq_value > 0x7F7F7F7F:
raise ValueError("VLQ 值超出最大范围(4 字节),非法 MIDI 文件")
return vlq_value
4.3.3 Track 事件分类与解析
Track 数据部分由一系列'Delta-Time + 事件'组成,事件分为三大类:通道事件(对应 3.2.1 节的通道消息)、系统通用事件(对应 3.2.2 节的系统消息)、元事件(MIDI 文件专属,用于描述乐曲信息)。
所有事件以状态字节开头,需结合'运行模式(Running Status)'解析,即连续相同类型、相同通道的事件可省略状态字节,直接复用前一事件的状态字节。
(1)元事件(Meta Event)解析(文件专属)
元事件仅存在于 MIDI 文件中,不发送给硬件设备,用于描述乐曲的元信息(如标题、作者、节拍、速度),状态字节固定为 0xFF,结构为:0xFF + 元事件类型(1 字节) + 数据长度(VLQ) + 数据(N 字节)。
| 元事件类型(十六进制) | 事件名称 | 数据格式 | 功能说明 | 解析示例 |
|---|
| 0x00 | 序列编号 | 数据长度 2 字节(大端序) | 标识当前序列编号(多序列文件用,较少见) | 0xFF 0x00 0x02 0x00 0x01 → 序列编号 1 |
| 0x01 | 乐曲标题 | 数据长度 VLQ,数据为 ASCII 字符串 | 存储乐曲名称,可用于文件解析后的展示 | 0xFF 0x01 0x05 48 65 6C 6C 6F → 标题'Hello' |
| 0x02 | 版权信息 | 数据长度 VLQ,数据为 ASCII 字符串 | 存储版权声明,非必需字段 | 0xFF 0x02 0x0A 4D 49 44 49 20 54 65 73 74 → 版权'MIDI Test' |
| 0x03 | 音轨名称 | 数据长度 VLQ,数据为 ASCII 字符串 | 标识当前音轨功能(如'旋律轨''鼓轨') | 0xFF 0x03 0x06 44 72 75 6D 20 54 72 61 63 6B → 音轨名'Drum Track' |
| 0x08 | 调号 | 数据长度 2 字节:字节 1 为调号(-7~+7),字节 2 为调式(0=大调,1=小调) | 定义乐曲调号,用于乐理分析和显示 | 0xFF 0x08 0x02 0x00 0x00 → C 大调 |
| 0x09 | 拍号 | 数据长度 4 字节:分子、分母(2 的幂次)、时钟数、三十二分音符数 | 定义乐曲节拍(如 4/4、3/4),配合 BPM 计算时长 | 0xFF 0x09 0x04 0x04 0x02 0x18 0x08 → 4/4 拍(分母 2²=4) |
| 0x0A | 速度(BPM) | 数据长度 3 字节(大端序),表示每四分音符的微秒数 | 核心参数,用于计算实际播放速度,BPM=60000000/微秒数 | 0xFF 0x0A 0x03 0x00 0x7A 0x12 → 微秒数 31250 → BPM=120 |
| 0x2F | 音轨结束 | 数据长度 0 字节 | 标识当前音轨事件结束,必需字段 | 0xFF 0x2F 0x00 → 音轨结束 |
(2)通道事件与系统事件解析
通道事件(0x80-0xEF)和系统事件(0xF0-0xF7、0xF8-0xFF)的结构与 3.2 节的 MIDI 消息一致,但需结合运行模式解析,步骤如下:
- 读取 Delta-Time(VLQ 解码),获取当前事件与上一事件的时间间隔;
- 读取 1 字节判断是否为状态字节(高位 bit=1):
- 是状态字节:记录当前状态字节,根据状态字节类型读取对应数量的数据字节;
- 非状态字节:复用上一状态字节,当前字节作为第一个数据字节,再读取剩余数据字节。
- 将'Delta-Time + 状态字节 + 数据字节'组合为完整事件,存入音轨事件列表。
开发提醒:系统实时事件(0xF8-0xFF,如时钟同步、启停)无 Delta-Time,可直接插入事件序列中,不影响其他事件的时间间隔。
4.3.4 Track Chunk 完整解析代码(Python)
结合 VLQ 解码、事件分类解析,实现 Track Chunk 全流程解析,返回结构化事件列表,可直接对接 Header Chunk 解析结果使用:
def parse_track_chunk(file, track_index):
"""
解析单个 Track Chunk,返回音轨信息及事件列表
:param file: 打开的 MIDI 文件对象(二进制读模式)
:param track_index: 音轨索引(从 0 开始)
:return: track_info: 字典,包含音轨名称、事件列表等信息
"""
chunk_id = file.read(4)
if chunk_id != b'MTrk':
raise ValueError(f"第{track_index+1}个音轨块格式非法:缺少 MTrk 标识")
chunk_length = struct.unpack('>I', file.read(4))[0]
track_end_pos = file.tell() + chunk_length
events = []
running_status = None
track_name = f"Track {track_index+1}"
while file.tell() < track_end_pos:
delta_time = decode_vlq(file)
current_byte = ord(file.read(1))
if current_byte & 0x80:
running_status = current_byte
if running_status == 0xFF:
meta_type = ord(file.read(1))
meta_len = 0
while True:
len_byte = ord(file.read(1))
meta_len = (meta_len << 7) | (len_byte & 0x7F)
if not (len_byte & 0x80):
break
meta_data = file.read(meta_len)
if meta_type == 0x03:
track_name = meta_data.decode('ascii', errors='replace')
elif meta_type == 0x2F:
events.append({
'delta_time': delta_time,
'event_type': 'Meta Event',
'meta_type': 'Track End',
'data': meta_data
})
break
events.append({
'delta_time': delta_time,
'event_type': 'Meta Event',
'meta_type': meta_type,
'data': meta_data
})
elif 0xF0 <= running_status <= 0xF7:
if running_status == 0xF0 or running_status == 0xF7:
sys_len = 0
while True:
len_byte = ord(file.read(1))
sys_len = (sys_len << 7) | (len_byte & 0x7F)
if not (len_byte & 0x80):
break
sys_data = file.read(sys_len)
else:
sys_data = file.read(1)
events.append({
'delta_time': delta_time,
'event_type': 'System Event',
'status': running_status,
'data': sys_data
})
elif 0x80 <= running_status <= 0xEF:
if 0xC0 <= running_status <= 0xDF:
data1 = ord(file.read(1))
events.append({
'delta_time': delta_time,
'event_type': 'Channel Event',
'status': running_status,
'channel': running_status & 0x0F,
'data': (data1,)
})
else:
data1 = ord(file.read(1))
data2 = ord(file.read(1))
events.append({
'delta_time': delta_time,
'event_type': 'Channel Event',
'status': running_status,
'channel': running_status & 0x0F,
'data': (data1, data2)
})
elif 0xF8 <= running_status <= 0xFF:
events.append({
'delta_time': 0,
'event_type': 'System Real-Time Event',
'status': running_status,
'data': b''
})
else:
if running_status is None:
raise ValueError(f"第{track_index+1}个音轨块非法:无运行状态字节")
if 0xC0 <= running_status <= 0xDF:
events.append({
'delta_time': delta_time,
'event_type': 'Channel Event',
'status': running_status,
'channel': running_status & 0x0F,
'data': (current_byte,)
})
else:
data2 = ord(file.read(1))
events.append({
'delta_time': delta_time,
'event_type': 'Channel Event',
'status': running_status,
'channel': running_status & 0x0F,
'data': (current_byte, data2)
})
track_info = {
'track_index': track_index,
'track_name': track_name,
'chunk_length': chunk_length,
'event_count': len(events),
'events': events
}
return track_info
def parse_midi_file(file_path):
"""
完整解析 MIDI 文件,返回 Header 信息和所有音轨信息
:param file_path: MIDI 文件路径
:return: midi_info: 字典,包含完整 MIDI 文件信息
"""
with open(file_path, 'rb') as f:
header_info = parse_header_chunk(f)
tracks = []
for i in range(header_info['num_tracks']):
track = parse_track_chunk(f, i)
tracks.append(track)
midi_info = {
'header': header_info,
'tracks': tracks,
'total_tracks': len(tracks)
}
return midi_info
if __name__ == "__main__":
midi_info = parse_midi_file("test.mid")
print("MIDI 文件解析完成:")
print(f"文件格式:{midi_info['header']['format_type']},音轨数:{midi_info['total_tracks']}")
for track in midi_info['tracks']:
print(f"音轨{track['track_index']+1}:{track['track_name']},事件数:{track['event_count']}")
4.3.5 Track Chunk 解析避坑指南
实际开发中,Track Chunk 解析易出现兼容性问题,核心避坑点如下:
- 运行模式处理不全:部分 MIDI 文件大量使用运行模式省略状态字节,若未复用前一状态字节,会导致事件解析错乱。解决方案:始终维护 running_status 变量,非状态字节时强制复用。
- VLQ 解码异常:非法 MIDI 文件可能存在超过 4 字节的 VLQ,导致死循环。解决方案:在 decode_vlq 函数中限制最大字节数(4 字节),超出则抛出异常。
- 音轨结束事件缺失:部分手动编辑的 MIDI 文件可能遗漏 0xFF 0x2F 0x00 事件,导致读取越界。解决方案:按 Track Chunk 长度字段严格控制读取范围,避免超出数据区。
- 系统实时事件插入:系统实时事件(如时钟同步)无 Delta-Time,可插入任意事件之间,解析时需单独处理,不影响 Delta-Time 累计。
4.4 MIDI 文件完整解析流程总结
结合 Header Chunk 和 Track Chunk 解析,MIDI 文件完整解析流程可分为 5 步,确保覆盖所有核心环节:
- 文件合法性校验:读取前 4 字节验证是否为'MThd',确认是 MIDI 文件;
- 解析 Header Chunk:读取后续 10 字节(4 字节长度 +6 字节数据),获取文件格式、音轨数、时间基准(Division),为 Track 解析奠定基础;
- 循环解析 Track Chunk:按音轨数循环,每个音轨先验证'MTrk'标识,再读取 Chunk 长度,逐事件解析(Delta-Time+ 事件内容),直至遇到音轨结束事件;
- 事件结构化处理:将解析后的事件按类型(元事件、通道事件、系统事件)分类存储,提取关键信息(音轨名、BPM、拍号);
- 时间换算与播放准备:结合 Division 和 BPM,将 Delta-Time(ticks)换算为实际时长(秒),为后续播放、编辑或 AI 处理提供时间基准。
五、Python Mido 库实操:生成、解析与进阶改造(完整代码)
前文从二进制层面实现了 MIDI 解析的底层逻辑,实际开发中可借助成熟库快速落地——Mido 是 Python 生态最常用的 MIDI 处理库,封装了底层协议细节,支持 MIDI 文件的解析、生成、编辑及设备交互,兼顾易用性和灵活性。
本节基于 Mido 库实现全流程实操,包含环境搭建、文件解析、乐曲生成、进阶改造(如音色替换、节奏调整),代码可直接复制运行。
5.1 环境搭建与库基础
5.1.1 安装 Mido 库
Mido 支持 Python 3.7+,通过 pip 安装,同时需安装 python-rtmidi 实现硬件设备交互(可选):
pip install mido
pip install mido python-rtmidi
5.1.2 Mido 核心概念
Mido 封装了 MIDI 协议的核心元素,关键概念对应关系如下,便于理解后续实操:
- MidiFile:对应 MIDI 文件,用于读取、写入和编辑完整 MIDI 文件;
- Track:对应 Track Chunk,每个 Track 对象包含一系列 Message 和 MetaMessage;
- Message:对应通道事件和系统事件(如 NoteOn、ControlChange);
- MetaMessage:对应元事件(如音轨名、速度、拍号);
- delta:对应 Delta-Time,单位为 ticks,与 MIDI 文件的 Division 一致。
5.2 Mido 解析 MIDI 文件(快速提取信息)
借助 Mido 可跳过底层二进制解析,直接提取 Header 信息、音轨事件、元数据等,适合快速开发需求:
import mido
def mido_parse_midi(file_path):
"""
使用 Mido 解析 MIDI 文件,提取关键信息
:param file_path: MIDI 文件路径
:return: 解析结果字典
"""
mid = mido.MidiFile(file_path)
header_info = {
'format_type': mid.type,
'num_tracks': len(mid.tracks),
'division': mid.ticks_per_beat,
'length_seconds': mid.length
}
tracks_info = []
for idx, track in enumerate(mid.tracks):
track_events = []
track_name = f"Track {idx+1}"
bpm = 120
for msg in track:
if msg.type == 'track_name':
track_name = msg.name
elif msg.type == 'set_tempo':
bpm = mido.tempo2bpm(msg.tempo)
if msg.type in ['note_on', 'note_off', 'program_change', 'control_change', 'set_tempo']:
track_events.append({
'delta_ticks': msg.time,
'event_type': msg.type,
'details': msg.dict()
})
tracks_info.append({
'track_index': idx,
'track_name': track_name,
'bpm': bpm,
'event_count': len(track_events),
'events': track_events
})
return {
'header': header_info,
'tracks': tracks_info
}
if __name__ == "__main__":
parse_result = mido_parse_midi("test.mid")
print("Header 信息:", parse_result['header'])
for track in parse_result['tracks']:
print(f"\n音轨{track['track_index']+1}:{track['track_name']}")
print(f"BPM:{track['bpm']},事件数:{track['event_count']}")
print("前 3 个事件:", track['events'][:3])
代码说明:Mido 自动处理二进制解析、VLQ 解码、运行模式等底层细节,可快速提取音轨名、BPM、音符事件等核心信息,大幅提升开发效率。
5.3 Mido 生成 MIDI 文件(从零创作乐曲)
通过 Mido 创建 MidiFile、Track、Message 对象,可从零生成自定义 MIDI 乐曲,适合 AI 作曲、自动配乐等场景,以下示例生成一段 C 大调旋律:
import mido
from mido import MidiFile, MidiTrack, Message, MetaMessage
def generate_simple_midi(output_path):
"""
生成一段简单的 C 大调旋律 MIDI 文件
:param output_path: 输出文件路径(.mid)
"""
mid = MidiFile(type=1, ticks_per_beat=96)
mid.tracks.append(melody_track)
melody_track.append(MetaMessage('track_name', name='C Major Melody', time=0))
melody_track.append(MetaMessage('set_tempo', tempo=mido.bpm2tempo(120), time=0))
melody_track.append(MetaMessage('time_signature', numerator=4, denominator=4, time=0))
melody_track.append(Message('program_change', program=0, channel=0, time=0))
melody_notes = [
(60, 48, 64),
(62, 48, 64),
(64, 48, 64),
(65, 48, 64),
(67, 96, 64),
(65, 48, 64),
(64, 48, 64),
(62, 96, 64),
(60, 192, 64),
]
for pitch, duration, velocity in melody_notes:
melody_track.append(Message('note_on', note=pitch, velocity=velocity, channel=0, time=0))
melody_track.append(Message('note_off', note=pitch, velocity=velocity, channel=0, time=duration))
melody_track.append(MetaMessage('end_of_track', time=0))
drum_track = MidiTrack()
mid.tracks.append(drum_track)
drum_track.append(MetaMessage('track_name', name='Drum Track', time=0))
drum_pattern = [
(36, 48, 80),
(42, 24, 60),
(38, 48, 80),
(42, 24, 60),
]
for _ in range(4):
for pitch, duration, velocity in drum_pattern:
drum_track.append(Message('note_on', note=pitch, velocity=velocity, channel=9, time=0))
drum_track.append(Message('note_off', note=pitch, velocity=velocity, channel=9, time=duration))
drum_track.append(MetaMessage('end_of_track', time=0))
mid.save(output_path)
print(f"MIDI 文件已生成:{output_path}")
if __name__ == "__main__":
generate_simple_midi("c_major_melody.mid")
代码说明:生成流程需遵循 MIDI 文件结构,先创建 MidiFile 对象,再添加音轨、元事件(速度、拍号)、音色设置,最后添加音符事件并保存。可通过调整音符的音高、时长、力度,生成任意旋律和节奏。
5.4 Mido 进阶改造:编辑现有 MIDI 文件
实际开发中常需对现有 MIDI 文件进行改造(如音色替换、节奏加速、添加效果),以下示例实现 3 种常见改造需求:
5.4.1 替换音色(钢琴→小提琴)
def replace_instrument(input_path, output_path, old_program, new_program, channel=0):
"""
替换 MIDI 文件指定通道的音色
:param input_path: 输入 MIDI 路径
:param output_path: 输出 MIDI 路径
:param old_program: 原音色编号
:param new_program: 新音色编号
:param channel: 目标通道(0-15)
"""
mid = mido.MidiFile(input_path)
for track in mid.tracks:
for msg in track:
if msg.type == 'program_change' and msg.channel == channel and msg.program == old_program:
msg.program = new_program
mid.save(output_path)
print(f"音色替换完成:通道{channel} 从{old_program}(钢琴)→{new_program}(小提琴)")
replace_instrument("test.mid", "violin_version.mid", old_program=0, new_program=41, channel=0)
5.4.2 调整节奏速度(加速/减速)
def adjust_tempo(input_path, output_path, speed_ratio):
"""
调整 MIDI 文件速度(比例系数)
:param input_path: 输入 MIDI 路径
:param output_path: 输出 MIDI 路径
:param speed_ratio: 速度比例(1.0=原速,1.5=加速 50%,0.8=减速 20%)
"""
mid = mido.MidiFile(input_path)
for track in mid.tracks:
new_messages = []
for msg in track:
msg.time = int(msg.time * speed_ratio)
if msg.type == 'set_tempo':
original_bpm = mido.tempo2bpm(msg.tempo)
new_bpm = original_bpm * speed_ratio
msg.tempo = mido.bpm2tempo(new_bpm)
new_messages.append(msg)
track[:] = new_messages
mid.save(output_path)
print(f"速度调整完成:{speed_ratio}倍速,新 BPM 约为原 BPM×{speed_ratio}")
adjust_tempo("test.mid", "fast_version.mid", speed_ratio=1.5)
5.4.3 添加混响效果(通过 CC 控制器)
def add_reverb(input_path, output_path, channel=0, reverb_depth=90):
"""
为指定通道添加混响效果(通过 CC91 控制器)
:param input_path: 输入 MIDI 路径
:param output_path: 输出 MIDI 路径
:param channel: 目标通道
:param reverb_depth: 混响深度(0-127,越大混响越强)
"""
mid = mido.MidiFile(input_path)
melody_track = mid.tracks[0]
insert_pos = 0
for idx, msg in enumerate(melody_track):
if msg.type == 'set_tempo':
insert_pos = idx + 1
break
melody_track.insert(insert_pos, Message('control_change', channel=channel, control=91, value=reverb_depth, time=0))
mid.save(output_path)
print(f"混响效果添加完成:通道{channel},混响深度{reverb_depth}")
add_reverb("test.mid", "reverb_version.mid", channel=0, reverb_depth=90)
5.5 Mido 与硬件设备交互(实时控制)
Mido 结合 python-rtmidi 可实现与 MIDI 硬件设备(如键盘、合成器)的实时交互,支持发送消息控制设备发声、接收设备输入的音符消息:
import mido
from mido import Message
def list_midi_devices():
"""列出所有可用的 MIDI 输入/输出设备"""
print("MIDI 输出设备:")
for idx, name in enumerate(mido.get_output_names()):
print(f" {idx}: {name}")
print("\nMIDI 输入设备:")
for idx, name in enumerate(mido.get_input_names()):
print(f" {idx}: {name}")
def send_note_to_device(device_idx, note, velocity=64, duration=1):
"""
向 MIDI 设备发送音符消息(控制发声)
:param device_idx: 输出设备索引
:param note: 音高(0-127)
:param velocity: 力度(0-127)
:param duration: 发声时长(秒)
"""
with mido.open_output(mido.get_output_names()[device_idx]) as port:
port.send(Message('note_on', note=note, velocity=velocity))
mido.sleep(duration)
port.send(Message('note_off', note=note, velocity=velocity))
def receive_notes_from_device(device_idx):
"""接收 MIDI 设备(如键盘)输入的音符消息"""
print(f"开始接收设备{device_idx}的音符消息(按 Ctrl+C 退出)")
with mido.open_input(mido.get_input_names()[device_idx]) as port:
for msg in port:
if msg.type in ['note_on', 'note_off']:
status = "按下" if msg.type == 'note_on' and msg.velocity > 0 else "松开"
print(f"音符{msg.note}(C4=60):{status},力度{msg.velocity}")
if __name__ == "__main__":
list_midi_devices()
代码说明:需先通过 list_midi_devices() 查看系统中的 MIDI 设备索引,再指定设备发送/接收消息。适用于开发 MIDI 键盘控制器、实时音效触发等硬件交互场景。
六、MIDI 2.0 协议核心升级:兼容性与新特性(前沿技术)
MIDI 1.0 协议自 1983 年发布以来,支撑了电子音乐、音频开发数十年的发展,但随着 AI 作曲、高保真音频、跨设备协同等需求的升级,其带宽限制、单向传输、低精度参数等短板逐渐凸显。2020 年 MIDI 制造商协会(MMA)与日本电子乐器工业协会(AMEI)联合发布 MIDI 2.0 协议,在保留向下兼容的基础上,实现了全方位技术突破,为新一代音乐开发提供了底层支撑。
本节将从升级背景、核心新特性、兼容性设计、落地现状及开发适配要点五个维度,拆解 MIDI 2.0 的技术细节,为开发者提供前沿技术参考。
6.1 升级背景:MIDI 1.0 的技术瓶颈与行业需求
MIDI 1.0 在设计之初针对的是 80 年代的电子乐器,受硬件性能限制,存在诸多难以突破的瓶颈,无法适配当下的技术场景:
- 参数精度不足:MIDI 1.0 所有数据字节均为 7 位(0-127),力度、弯音、控制器等参数精度较低,无法实现细腻的音色表达和演奏控制,难以满足专业音乐制作、高保真音效开发的需求。
- 单向传输限制:MIDI 1.0 仅支持设备间单向消息传输(如键盘→合成器),无法实现设备状态反馈(如合成器音色当前参数、旋钮位置回传至电脑),不利于多设备协同控制和智能化操作。
- 带宽与通道局限:传输速率固定为 31250 bps,仅支持 16 个通道,面对多轨复杂乐曲、多设备同步场景时,易出现消息丢失、卡顿;且无统一的设备配置协议,不同厂商设备适配成本高。
- AI 与跨平台适配弱:缺乏标准化的元数据、参数描述协议,AI 作曲模型难以精准解析设备特性;对移动端、嵌入式设备的适配性不足,无法满足物联网时代的跨终端音乐开发需求。
基于上述瓶颈,MIDI 2.0 以'高精度、双向交互、高兼容性、可扩展'为核心目标,进行了全协议层的重构与升级,同时严格保证与 MIDI 1.0 设备的向下兼容,降低行业迁移成本。
6.2 MIDI 2.0 核心新特性(协议层突破)
MIDI 2.0 并非对 1.0 的简单修补,而是在消息结构、参数精度、传输机制、设备交互等层面实现了颠覆性升级,核心特性可概括为'高精度、双向化、可扩展、智能化'四大维度。
6.2.1 16 位高精度参数:细腻控制的底层支撑
MIDI 2.0 将核心参数精度从 1.0 的 7 位(0-127)提升至 16 位(0-65535),同时保留 7 位模式供兼容使用,大幅提升了音乐表达的细腻度。
- 扩展数据字节结构:针对原有的通道消息(如 Note On、Control Change),新增 16 位数据传输模式,通过'状态字节 + 扩展标识 +16 位数据'的结构,实现高精度参数传输。例如,16 位力度参数可精准区分从极弱到极强的细微变化,适配古典乐器、专业录音的需求。
- 弯音范围扩展:MIDI 1.0 弯音默认范围为±2 个半音,且精度较低;MIDI 2.0 支持 16 位弯音精度,同时可通过协议动态配置弯音范围(最高±48 个半音),实现更灵活的滑音、颤音效果。
- 控制器参数细化:128 个 CC 控制器均支持 16 位精度,混响、延迟、均衡器等音效参数可实现平滑调节,避免 1.0 时代的阶梯式变化,提升听觉体验。
开发注意:16 位参数传输需区分'原生 16 位模式'与'7 位兼容模式',可通过设备协商机制自动适配,确保对 1.0 设备的兼容。
6.2.2 双向通信机制:设备协同与状态反馈
双向通信是 MIDI 2.0 最核心的升级之一,打破了 1.0 单向传输的局限,实现'指令下发 + 状态回传'的闭环交互,为多设备协同、智能化控制奠定基础。
- 属性交换协议(Property Exchange Protocol):标准化设备属性的查询与设置流程,上位机(如电脑、手机)可主动查询设备的能力参数(如支持的音色库、控制器范围、精度等级),设备也可主动上报状态变化(如旋钮调节、音色切换、故障提示)。
- 配置文件同步:支持设备配置文件(如音色预设、控制器映射、演奏参数)的双向同步,例如将电脑中编辑好的音色配置推送至合成器,或把合成器的自定义预设备份至电脑,提升开发与使用效率。
- 实时状态反馈:演奏过程中,设备可实时回传演奏参数(如当前按下的音符、力度曲线、弯音变化),上位机可基于反馈进行实时调整,适配 AI 辅助演奏、远程控制等场景。
应用场景:在 AI 作曲辅助中,上位机可根据合成器回传的音色特性,动态调整生成的 MIDI 序列,确保演奏效果与设备匹配;在舞台演出中,控制台可实时监控所有设备状态,实现精准同步控制。
6.2.3 扩展通道与带宽:多设备与复杂场景适配
MIDI 2.0 突破了 1.0 的 16 通道限制,同时提升了传输带宽,适配多设备、多轨复杂乐曲的开发需求。
- 通道扩展:支持最多 256 个通道(0-255),分为 16 个'组'(每组 16 个通道),可通过组标识快速区分设备类型(如旋律设备、鼓组设备、效果器设备),简化多设备协同的通道管理。
- 带宽提升:传输速率从 1.0 的 31250 bps 提升至最高 125000 bps(USB-MIDI 场景下可更高),同时优化消息编码方式,减少冗余数据,大幅降低多消息并发传输时的卡顿与丢失概率。
- 时间同步优化:升级 MIDI Clock 同步机制,支持亚毫秒级时间精度,多设备同步误差可控制在 1ms 以内,适配专业录音棚、舞台演出等对同步性要求极高的场景。
6.2.4 可扩展元数据与标准化配置:跨平台与 AI 适配
MIDI 2.0 新增标准化的元数据协议和设备配置框架,解决了 1.0 时代厂商自定义格式导致的兼容性问题,同时适配 AI 作曲、跨平台开发的需求。
- 标准化元数据:支持为 MIDI 文件、设备参数添加标准化元数据(如乐曲风格、音色类型、演奏者信息、设备型号),AI 模型可通过元数据快速解析内容特性,提升生成效果的精准度。
- 通用设备配置框架:定义了统一的设备能力描述格式,不同厂商设备需按标准上报自身支持的功能(如是否支持 16 位精度、最大通道数、音效类型),上位机可通过统一接口适配所有 MIDI 2.0 设备,无需针对厂商单独开发驱动。
- 模块化扩展:支持自定义消息模块,厂商可在标准协议基础上扩展专属功能(如特殊音效控制、固件升级),同时不影响与标准设备的兼容性,兼顾标准化与个性化需求。
6.2.5 原生支持无线与嵌入式设备:跨终端适配
MIDI 2.0 原生支持 BLE MIDI、WiFi MIDI 等无线传输方式,同时优化了对嵌入式设备、移动端的适配,满足物联网时代的跨终端音乐开发需求。
- 无线传输优化:针对 BLE MIDI 优化消息编码,降低传输延迟(可控制在 5ms 以内),同时提升抗干扰能力,适配舞台演出、移动端音乐制作等无线场景。
- 低功耗适配:优化协议栈设计,降低设备功耗,支持嵌入式设备(如智能乐器、便携音效器)长时间运行,适配物联网终端的低功耗需求。
- 跨系统兼容:Windows、macOS、Linux、iOS、Android 均已原生支持 MIDI 2.0 协议,开发者可基于统一 API 实现多平台应用开发,无需额外适配系统差异。
6.3 MIDI 2.0 兼容性设计:向下兼容与平滑迁移
为降低行业迁移成本,MIDI 2.0 采用'双模式兼容'设计,确保 MIDI 1.0 设备可与 2.0 设备无缝协同,同时支持开发者逐步迭代升级应用。
6.3.1 硬件兼容性:双模式设备与协议转换
- 双模式设备:主流 MIDI 2.0 设备均支持'MIDI 1.0 模式'与'MIDI 2.0 模式'切换,可自动识别连接设备的协议版本,适配不同世代的硬件。例如,MIDI 2.0 键盘连接 1.0 合成器时,自动切换至 1.0 模式,确保消息正常传输。
- 协议转换设备:针对老旧 1.0 设备,可通过 MIDI 2.0 协议转换盒实现双向协议转换,将 2.0 消息转换为 1.0 格式下发至旧设备,同时将旧设备的状态反馈转换为 2.0 格式回传至上位机,实现新旧设备协同。
6.3.2 软件与文件兼容性:向后兼容与增量升级
- 文件兼容性:MIDI 2.0 文件格式(.mid2)支持包含 1.0 兼容数据块,1.0 播放器可读取兼容数据块正常播放,2.0 播放器可读取完整数据(含 16 位参数、元数据),实现文件的跨版本复用。
- 软件适配:主流音频开发库(如 Python Mido、C++ RtMidi)均已支持 MIDI 2.0 协议,提供兼容 API,开发者可通过开关控制使用 1.0 或 2.0 特性,无需重构现有代码,实现增量升级。
6.4 MIDI 2.0 落地现状与开发适配要点
6.4.1 落地现状
目前 MIDI 2.0 已逐步进入商业化落地阶段,主流厂商(Yamaha、Roland、Korg、Native Instruments)均已发布 2.0 设备(如合成器、键盘、接口盒);软件层面,Ableton Live、Logic Pro、Cubase 等专业 DAW 均已支持 MIDI 2.0 特性;开发库层面,Mido 1.3.0+、RtMidi 5.0+、CoreMIDI(macOS 11+)、MMMIDI API(Windows 11+)均提供完整的 2.0 协议支持。
但需注意,民用市场仍以 MIDI 1.0 设备为主,2.0 设备主要集中在专业领域(录音棚、舞台演出、高端电子乐器),开发者需兼顾新旧协议的适配需求。
6.4.2 开发适配要点
- 协议版本协商:开发时需先通过属性交换协议查询设备支持的协议版本,自动切换适配逻辑,避免在 1.0 设备上调用 2.0 特性导致兼容问题。
- 参数精度适配:针对 16 位参数,需提供 7 位兼容降级方案,确保在 1.0 设备上可正常工作;同时优化参数处理逻辑,充分利用 2.0 的高精度特性提升效果。
- 双向交互开发:基于 2.0 的双向通信机制,重构设备交互逻辑,新增状态反馈处理模块,适配设备参数回传、实时监控等功能。
- 跨平台适配:针对不同系统的 MIDI 2.0 API 差异,封装统一接口,确保应用在 Windows、macOS、移动端的一致性;同时适配无线传输场景,优化消息抗干扰与延迟控制。
6.5 未来趋势:MIDI 2.0 与 AI、物联网的融合
MIDI 2.0 的升级为音乐开发与新兴技术的融合奠定了基础,未来核心趋势包括:
- AI 作曲深度融合:基于 2.0 的高精度参数、标准化元数据,AI 模型可生成更细腻、更贴合设备特性的 MIDI 序列,同时通过设备状态反馈动态优化生成结果,实现'AI 创作 + 设备适配'的闭环。
- 物联网智能乐器:基于 2.0 的低功耗、无线传输、双向交互特性,可实现智能乐器的互联互通(如智能钢琴、吉他、鼓组的协同演奏),同时通过云端同步配置文件、演奏数据,构建物联网音乐生态。
- 沉浸式音频开发:结合 16 位高精度参数与多通道扩展,MIDI 2.0 可适配全景声、空间音效等沉浸式音频场景,实现更精准的音效定位与细腻控制,应用于游戏、VR/AR、影视配乐等领域。
综上,MIDI 2.0 并非简单的协议升级,而是开启了'高精度、智能化、跨终端'的音乐开发新时代。开发者需提前掌握其核心特性与适配逻辑,才能在专业音频开发、AI 作曲、物联网音乐设备等前沿领域占据先机。
七、全量附录:标准表、工具集与常见问题排查(开发必备)
本节整理 MIDI 开发高频使用的标准表、工具集及常见问题解决方案,可作为项目开发的工具书直接复用,大幅提升开发效率,规避常见坑点。
7.1 核心标准表(开发直接复用)
7.1.1 GM 标准音色表(128 种基础音色,通道 1-9、11-16 适用)
GM(General MIDI)标准统一了音色编号,确保不同设备播放一致性,是开发必备基础表,按编号对应如下:
- 钢琴类(1-8):大钢琴、亮音钢琴、电钢琴 1、电钢琴 2、羽管键琴、击弦古钢琴、vibes、马林巴
- 管风琴类(9-16):管风琴、低音管风琴、手风琴、口琴、班卓琴、曼陀林、大阮、三味线
- 吉他类(17-24):尼龙弦吉他、钢弦吉他、爵士吉他、清音电吉他、闷音电吉他、过载电吉他、失真电吉他、吉他泛音
- 贝斯类(25-32):无品贝斯、指弹贝斯、拨片贝斯、slapped 贝斯 1、slapped 贝斯 2、电贝斯、木贝斯、倍低音提琴
- 弦乐类(33-40):小提琴、中提琴、大提琴、低音提琴、弦乐合奏 1、弦乐合奏 2、竖琴、定音鼓
- 铜管类(41-48):小号、长号、圆号、大号、铜管合奏、合成铜管 1、合成铜管 2、萨克斯风
- 木管类(49-56):高音萨克斯、次中音萨克斯、上低音萨克斯、双簧管、英国管、单簧管、低音单簧管、长笛
- 吹管类(57-64):短笛、长笛 2、排箫、低音管、奥卡雷那笛、口哨、ocarina、合成吹管
- 打击乐类(65-72):合成弦乐 1、合成弦乐 2、合成垫音 1(氛围)、合成垫音 2(温暖)、合成垫音 3(多项式)、合成垫音 4(合唱)、合成垫音 5(氛围)、合成垫音 6(亮音)
- 合成 lead 类(73-80):合成 lead1(方波)、合成 lead2(锯齿波)、合成 lead3(.calliope)、合成 lead4(chop)、合成 lead5(弦乐)、合成 lead6(管乐)、合成 lead7(复音)、合成 lead8(低音)
- 合成音效类(81-88):合成音效 1(雨)、合成音效 2(音效)、合成音效 3(水晶)、合成音效 4(大气)、合成音效 5(闪光)、合成音效 6(杂音)、合成音效 7(哨音)、合成音效 8(科幻)
- 民族乐器类(89-96):丁巴鼓、钢鼓、木鱼、定音鼓、管钟、钹、雨声器、铃鼓
- 打击乐扩展(97-104):合成鼓、康加鼓、邦戈鼓、timbale、铜鼓、低音鼓、小手鼓、铃鼓 2
- 其他音色(105-128):颤音琴、木琴、管钟、磬、桑巴鼓、奎卡鼓、哨子、刮擦声、蜂鸣器、机械声、鸟鸣、电话铃声、直升机、applause、枪声、引擎声
7.1.2 GM 标准鼓组表(通道 10 专属,音高对应音色)
通道 10 为 GM 标准鼓组通道,不响应 Program Change 消息,通过 Note On 音高控制鼓组音色,核心音高与音色对应如下:
音高(十进制) | 音色名称 | 音高(十进制) | 音色名称 | 音高(十进制) | 音色名称 |
35 | 低音鼓 1 | 53 | 拍手 | 71 | 钟铃 |
36 | 低音鼓 2 | 54 | 电颤琴 | 72 | 牛铃 |
37 | 边击底鼓 | 55 | 高音康加鼓 | 73 | 木鱼(高) |
38 | 军鼓 1 | 56 | 低音康加鼓 | 74 | 木鱼(低) |
39 | 边击军鼓 | 57 | 开镲 1 | 75 | tambourine |
40 | 军鼓 2 | 58 | 高邦戈鼓 | 76 | 响板 |
41 | 低嗵鼓 1 | 59 | 低邦戈鼓 | 77 | 三角铁(高) |
42 | 闭镲 1 | 60 | 开镲 2 | 78 | 三角铁(低) |
43 | 低嗵鼓 2 | 61 | 踩镲踏板 | 79 | 振动器 |
44 | 闭镲 2 | 62 | 高音嗵鼓 1 | 80 | 响铃 |
45 | 中嗵鼓 1 | 63 | 高音嗵鼓 2 | 81 | 铃鼓 |
46 | 开镲 3 | 64 | 铃鼓 | 82 | 钹(高) |
47 | 中嗵鼓 2 | 65 | 牛铃(低) | 83 | 钹(低) |
48 | 高嗵鼓 | 66 | 牛铃(高) | 84 | 雨声器 |
49 | 击镲 1 | 67 | 木鱼(中) | 85 | 风声器 |
50 | 击镲 2 | 68 | 定音鼓(高) | 86 | 雷鸣器 |
51 | 吊镲 1 | 69 | 定音鼓(中) | 87 | 海浪声 |
52 | 吊镲 2 | 70 | 定音鼓(低) | 88 | 鸟鸣声 |
7.1.3 核心 CC 控制器功能表(CC0-CC127 高频使用项)
Control Change 消息(0xBn)支持 128 个控制器,以下为开发高频使用的控制器及功能,剩余控制器为厂商自定义或预留:
CC 编号 | 功能名称 | 取值范围 | 核心说明 |
0 | 银行选择(MSB) | 0-127 | 配合 CC32 切换扩展音色库,高位字节 |
1 | 调制轮 | 0-127 | 控制颤音、音色亮度,默认对应调制深度 |
2 | 呼吸控制器 | 0-127 | 模拟呼吸力度,控制音量或音色变化 |
4 | 脚踏控制器 1 | 0-127 | 自定义功能,常见用于表情控制 |
5 | 端口amento 时间 | 0-127 | 滑音时间调节,值越大滑音越慢 |
6 | 数据入口 | 0-127 | 配合 CC98/99 设置 14 位参数,低位字节 |
7 | 主音量 | 0-127 | 控制对应通道整体音量,0=静音,127=最大 |
8 | 平衡 | 0-127 | 左右声道平衡,64=居中 |
10 | 声像 | 0-127 | 通道声像定位,0=左声道,127=右声道 |
11 | 表情强度 | 0-127 | 细腻音量调节,比 CC7 更精准 |
12 | 效果控制 1 | 0-127 | 自定义效果参数,常见用于混响前置调节 |
13 | 效果控制 2 | 0-127 | 自定义效果参数,常见用于延迟前置调节 |
16 | 通用滑块 1 | 0-127 | 自定义滑块控制,适配设备旋钮 |
17 | 通用滑块 2 | 0-127 | 自定义滑块控制,适配设备旋钮 |
18 | 通用滑块 3 | 0-127 | 自定义滑块控制,适配设备旋钮 |
19 | 通用滑块 4 | 0-127 | 自定义滑块控制,适配设备旋钮 |
32 | 银行选择(LSB) | 0-127 | 配合 CC0 切换扩展音色库,低位字节 |
64 | 延音踏板(sostenuto) | 0-127 | 0-63=抬起,64-127=按下,控制音符延音 |
65 | 端口amento 开关 | 0-127 | 0-63=关闭,64-127=开启,控制滑音功能 |
66 | sostenuto 踏板 | 0-127 | 保持踏板,仅保持按下踏板时的音符 |
67 | 软踏板 | 0-127 | 减弱音量并柔化音色,值越大效果越明显 |
71 | 谐振(亮度) | 0-127 | 控制音色明亮度,值越大音色越亮 |
72 | 释放时间 | 0-127 | 控制音符释放时长,值越大衰减越慢 |
73 | Attack 时间 | 0-127 | 控制音符起音时长,值越大起音越缓 |
74 | 亮度控制 | 0-127 | 辅助谐振调节,细化音色明亮度 |
75 | 衰减时间 | 0-127 | 控制音符衰减时长,值越大衰减越慢 |
76 | 延音时间 | 0-127 | 控制音符延音阶段时长 |
77 | 震音速率 | 0-127 | 控制颤音频率,值越大颤音越快 |
78 | 震音深度 | 0-127 | 控制颤音幅度,值越大颤音越明显 |
79 | 震音延迟 | 0-127 | 控制颤音启动延迟,值越大延迟越久 |
80 | 端口amento 深度 | 0-127 | 控制滑音幅度,值越大滑音越明显 |
91 | 混响深度 | 0-127 | 控制混响效果强度,0=无混响 |
92 | 延迟深度 | 0-127 | 控制延迟效果强度,0=无延迟 |
93 | 合唱深度 | 0-127 | 控制合唱效果强度,0=无合唱 |
94 | 音色过滤 | 0-127 | 控制音色滤波强度,细化音色质感 |
95 | 效果音量 | 0-127 | 控制整体效果音量,平衡干声与效果声 |
96 | 数据增量 | 0-127 | 增加数据参数值,配合数据入口使用 |
97 | 数据减量 | 0-127 | 减少数据参数值,配合数据入口使用 |
98 | 非注册参数(MSB) | 0-127 | 厂商自定义参数,高位字节 |
99 | 非注册参数(LSB) | 0-127 | 厂商自定义参数,低位字节 |
100 | 注册参数(MSB) | 0-127 | 标准化注册参数,高位字节 |
101 | 注册参数(LSB) | 0-127 | 标准化注册参数,低位字节 |
121 | 重置所有控制器 | 0 | 重置对应通道所有 CC 控制器至默认值 |
122 | 本地控制开关 | 0-127 | 0=关闭本地控制,64-127=开启 |
123 | 所有音符关闭 | 0 | 停止对应通道所有发声音符 |
124 | 所有声音关闭 | 0 | 停止对应通道所有声音(含效果声) |
125 | 单音模式开关 | 0-127 | 0=复音模式,64-127=单音模式 |
126 | 复音模式开关 | 0-127 | 0=单音模式,64-127=复音模式 |
127 | 触后灵敏度 | 0-127 | 调节触后效果灵敏度,值越大越灵敏 |
7.2 开发必备工具集(分场景推荐)
7.2.1 协议解析与调试工具
- MIDI Monitor(Windows/macOS):实时监控 MIDI 消息传输,支持十六进制、中文解析,可查看 Delta-Time、事件类型,适配 1.0/2.0 协议,调试设备通信必备。
- MIDI Ox(Windows):功能强大的 MIDI 调试工具,支持消息捕获、编辑、发送,可模拟 MIDI 设备发送指令,排查硬件适配问题。
- Snoize MIDI Monitor(macOS):原生支持 macOS,界面简洁,支持 USB-MIDI、BLE MIDI 消息监控,适配 CoreMIDI API,适合苹果生态开发。
- MIDI Analyzer(在线工具):无需安装,上传 MIDI 文件即可解析 Chunk 结构、事件序列、Delta-Time,生成结构化报告,快速定位文件解析问题。
7.2.2 开发库与框架(多语言适配)
- Python 开发:
- Mido:轻量级 MIDI 处理库,支持 MIDI 文件解析、生成、消息发送,适配 1.0/2.0 协议,API 简洁,文档完善,适合快速开发。
- Music21:专注于音乐理论与 MIDI 结合,支持和弦分析、调号识别、MIDI 序列处理,适合 AI 作曲、乐理工具开发。
- PyGame:自带 MIDI 模块,支持设备端口调用、消息发送,适合游戏音效开发场景。
- C/C++ 开发:
- RtMidi:跨平台 MIDI 开发库,支持 Windows、macOS、Linux,适配 1.0/2.0 协议,可调用硬件端口、处理消息,性能优异。
- PortMidi:轻量级跨平台库,API 简单,适合嵌入式设备、桌面应用开发,兼容性强。
- 移动端开发:
- iOS:CoreMIDI 框架,原生支持 MIDI 2.0、BLE MIDI,适配 iPhone、iPad,可与系统音频设备无缝交互。
- Android:MIDI API(Android 6.0+),支持 USB-MIDI、BLE MIDI,提供设备管理、消息处理接口。
- 前端开发:
- Web MIDI API:浏览器原生支持,可通过 JavaScript 调用本地 MIDI 设备,实现网页端音乐工具、AI 作曲演示。
7.2.3 音色与文件处理工具
- 软音源:
- General MIDI SoundFont:通用 GM 音色库,适合测试 MIDI 文件播放,确保兼容性。
- Native Instruments Kontakt:专业软音源平台,支持多种音色库,适配高精度 MIDI 2.0 参数。
- Yamaha XG SoundFont:适配 XG 标准,扩展音色与效果器,提升 MIDI 播放音质。
- 文件编辑:
- Ableton Live:专业 DAW,支持 MIDI 1.0/2.0 文件编辑、多轨混音,适合音乐制作与工程落地。
- Logic Pro(macOS):苹果生态专业 DAW,原生支持 MIDI 2.0,内置丰富软音源与效果器。
- MuseScore:开源 MIDI 编辑工具,支持 MIDI 文件导入导出、乐谱编辑,适合乐理教学工具开发。
7.2.4 硬件测试工具
- MIDI 接口盒:推荐 Roland UM-ONE、Yamaha UX16,支持多进多出,适配 USB-MIDI,用于多设备串联测试。
- BLE MIDI 适配器:用于测试无线 MIDI 传输,适配移动端、嵌入式设备,验证延迟与抗干扰能力。
- 虚拟 MIDI 设备:LoopBe1(Windows)、IAC Driver(macOS),可创建虚拟端口,实现软件间 MIDI 消息传输,无需硬件即可调试。
7.3 常见问题排查(开发避坑指南)
7.3.1 硬件连接类问题
| 问题现象 | 常见原因 | 解决方案 |
|---|
| 设备无法被系统识别 | 1. 驱动未安装;2. USB 线故障;3. 端口冲突;4. 设备供电不足 | 1. 安装厂商官方驱动(小众设备),主流设备即插即用;2. 更换优质 USB 线,尝试不同 USB 端口;3. 关闭占用端口的其他程序,重启电脑;4. 为设备单独供电(部分大功率设备) |
| 多设备串联无响应 | 1. 端口连接错误(如 OUT 接 OUT);2. 信号衰减;3. 未开启 Thru 端口 | 1. 严格按'OUT→IN'连接,Thru 端口用于转发;2. 减少串联设备数量(≤5 台),改用星型拓扑;3. 确认设备 Thru 端口开启,部分设备默认关闭 |
| 无线 MIDI 延迟过高 | 1. 距离过远;2. 干扰严重;3. 设备功耗设置过低 | 1. 缩短设备距离(BLE 建议≤10 米);2. 避开 WiFi、蓝牙干扰源;3. 调整设备功耗模式,优先保证传输速率 |
7.3.2 协议解析类问题
| 问题现象 | 常见原因 | 解决方案 |
|---|
| MIDI 文件解析失败 | 1. 缺少 MThd/MTrk 标识;2. 大端序转换错误;3. VLQ 解码异常;4. 事件未结束(无 0xFF 0x2F 0x00) | 1. 验证文件开头标识,非法文件需过滤;2. 所有多字节数据使用大端序转换(如 Python struct 的'>'符号);3. 限制 VLQ 最大 4 字节,避免死循环;4. 确保解析至音轨结束事件,补全缺失事件 |
| 运行模式下消息解析错乱 | 1. 未记录上一状态字节;2. 系统实时消息干扰 | 1. 维护状态字节变量,非状态字节时复用前一状态;2. 系统实时消息(0xF8-0xFF)无 Delta-Time,单独处理,不影响状态字节记录 |
| 时间计算偏差 | 1. Division 格式解析错误;2. BPM 未正确读取;3. VLQ 解码错误 | 1. 区分 PPQ 与 SMPTE 格式,正确解析时间基准;2. 读取元事件中的速度信息(0xFF 0x0A),计算 BPM;3. 验证 VLQ 解码逻辑,对照示例数据测试 |
7.3.3 兼容性类问题
| 问题现象 | 常见原因 | 解决方案 |
|---|
| 音色错乱 | 1. 通道 10 误发 Program Change;2. 未适配 GM/GS/XG 标准;3. 音色库缺失 | 1. 通道 10 为鼓组通道,禁止发送 Program Change;2. 优先使用 GM 标准音色,如需扩展适配 GS/XG;3. 加载对应 SoundFont 音色库,确保音色编号匹配 |
| MIDI 2.0 消息无法识别 | 1. 设备仅支持 1.0 协议;2. 未协商协议版本;3. 16 位参数未降级 | 1. 检测设备协议版本,自动切换至 1.0 模式;2. 通过属性交换协议协商版本,避免调用不支持的特性;3. 16 位参数降级为 7 位(右移 9 位),确保 1.0 设备兼容 |
| 控制器参数无响应 | 1. CC 编号错误;2. 设备不支持该控制器;3. 未重置控制器状态 | 1. 对照 CC 控制器表验证编号,避免使用预留编号;2. 查询设备能力参数,仅调用支持的控制器;3. 初始化时发送 CC121(重置控制器),恢复默认状态 |
7.3.4 开发库使用类问题
| 问题现象 | 常见原因 | 解决方案 |
|---|
| Mido 库无法读取设备 | 1. 端口名称错误;2. 权限不足;3. 库版本过低 | 1. 通过 mido.get_input_names()/get_output_names() 获取端口名称;2. 赋予程序设备访问权限(Linux/macOS);3. 升级 Mido 至 1.3.0+,支持 MIDI 2.0 |
| RtMidi 库编译失败 | 1. 缺少依赖库;2. 跨平台适配错误 | 1. 安装 PortAudio、ALSA 等依赖(Linux);2. 配置跨平台编译选项,确保 API 适配对应系统(如 Windows 用 MMMIDI,macOS 用 CoreMIDI) |
| Web MIDI API 无法调用设备 | 1. 浏览器不支持;2. 未获取用户授权;3. 仅支持 HTTPS | 1. 使用 Chrome、Edge 等现代浏览器;2. 调用 API 时请求用户授权,获取设备访问权限;3. 本地测试可使用 localhost,线上需部署 HTTPS |
7.4 补充说明
- 标准表适配:GM 标准为基础兼容标准,GS/XG 为扩展标准,开发时优先适配 GM,如需扩展功能再兼容 GS/XG,确保最大兼容性。
- 工具选型:调试阶段优先使用虚拟设备与监控工具,快速定位问题;落地阶段结合实际硬件与软音源,验证播放效果。
- 协议迭代:MIDI 2.0 仍在普及中,开发时需预留版本适配接口,便于后续升级支持高精度、双向交互等特性。
本附录可作为开发手册随时查阅,结合前文的协议解析与实操代码,可覆盖从需求开发到问题排查的全流程,助力高效落地 MIDI 相关项目。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online