15  第十五章 MIDI通信

月华羞,捧金瓯。歌扇萦风,吹散一春愁。

15.1 MIDI 介绍

MIDI是Musical Instrument Digital Interface(乐器数字接口)的简称,它并不是指某种特殊的音乐设备,而是一种通信协议,用于传递音乐演奏过程中的必要信息。

上世纪70~80年代电子合成器开始逐渐普及,但不同厂商设备之间没有统一的接口,因此要跨越两个不同品牌的设备进行协同演奏是一件很难的事情,此时迫切需要一种统一的通信协议来规范这些电子音乐设备,MIDI正是基于此而诞生的。1981年Dave Smith首次提出MIDI概念并由Sequential和Roland一起牵手开发,1983年MIDI 1.0正式发布(2020 年更新了 MIDI 2.0 标准)。MIDI是基于异步串口通信(UART)的协议,异步串口通信指的是数据发送的方法,MIDI协议规定的是以怎样的格式来发送数据。

15.2 UART

UART是通用异步收发器(Universal Asynchronous Receiver Transmitter)的简写,也就是我们常说的“串口”,UART需要3条线,分别是TX(发送数据)、RX(接收数据)和GND(公共地线):

图 15.1: UART连接方式

从上图中会看到,UART不需要连接同步时钟信号,通信双方依靠波特率和起始位、停止位来收发数据,波特率规定了每秒钟传递的数据个数,常见的波特率有9600、19200等,MIDI信号的波特率为31250。

UART传递的每组数据被称为“帧”,一帧的结构包含起始位、数据位、校验位、停止位。

在没有数据传输时,发送设备的TX保持高电平输出为1,接收设备的 RX 不会接收数据,当需要传递信号时,TX会由高电平变为低电平(由1变为0),接收设备通过检测电平变化以准备接收数据。之后TX会发送连续的8位(一个字节)的数据,接收设备会接收这8位数据,最后TX会发送停止位数据,表示本次信号传递结束:

图 15.2: 一帧数据

在Arduino上UART的发送和接收引脚分别是1号和0号引脚,通常情况下不建议把这两个引脚用于其他功能,要实现输出,可以简单配置Serial:

int i = 0;
void setup(){
Serial.begin(31250);
}
void loop(){
Serial.write(i);
i += 1;
delay(1000);
}

打开串口助手,就能看到电脑不断接收到由Arduino传递的信号了。

15.3 MIDI 信号格式

一个MIDI信号通常包含3 个字节(3 Bytes):1个状态字节和2个数据字节,状态字节以1开头,数据字节以0开头:

图 15.3: MIDI数据结构

状态字节低4位(从右到左)表示MIDI通道号,可以设置为通道1(0000)到通道16(1111),MIDI设备可以设置自己是通道几,防止干扰。高4位表示不同操作行为,例如最基本的开启音符和关闭音符,状态字节分别是1001xxxx、1000xxxx。

第一个数据字节(0kkkkkkk)表示演奏的音符号(范围从0到127),0表示C1、60表示C4、127表示G9,第二个数据字节表示演奏的力度(范围从0到127)。例如发送10010000-00111100-01010101表示通道1的MIDI设备以85的力度演奏音符C4。

15.4 连接到 DAW

DAW是数字音频工作站的缩写,例如电脑软件Ableton。但我们无法直接把串口数据发送给Ableton,因为Ableton是无法直接访问串口的,它需要接收来自“MIDI端口”的数据,我们得先使用桥接软件(我使用的是EA Serial MIDI Bridge)把串口数据转化成一个虚拟的MIDI端口数据。

以macOS为例,找到系统内置的“音频MIDI设置”软件,左上角窗口选择“显示MIDI工作室”,会看到如下界面:

图 15.4: MIDI工作室界面

双击“IAC驱动程序”,点击“设备在线”,然后点击“应用”,然后会看到 图 15.4 的图标不再显示灰色,此时已经设置好了一个虚拟的MIDI端口。

之后打开桥接软件(EA Serial MIDI Bridge):

图 15.5: 桥接软件

最下方先设置好波特率(它只提供了几个固定的选项,但没关系,可以选择9600),Serial Port选择电脑与Arduino连接的端口,MIDI Out选择“IAC驱动程序”里激活了的端口。

最后打开Ableton:

图 15.6: Ableton界面

要先选择一个声源,之后设置好“MIDI From”处的信号源和通道。这样就配置完成了,现在从Arduino发送数据就能在Ableton里听到。

假设我们想先发送C4,一段时间后关闭C4,再发送G9,最后关闭G9,代码如下:

void setup() {
Serial.begin(9600); // 初始化串口为 MIDI 标准波特率
}
void loop() {
// 发送开启音符(通道1, 音符60=C4, 力度100)
Serial.write(0x90); // 状态字节:Note On, 通道1
Serial.write(60); // 数据字节1:音符号 (C4)
Serial.write(100); // 数据字节2:力度 (0–127)
delay(500);
// 发送关闭音符(通道1, 音符60=C4, 力度0)
Serial.write(0x80); // 状态字节:Note Off, 通道1
Serial.write(60); // 数据字节1:音符号
Serial.write(0); // 数据字节2:力度
delay(1000);
Serial.write(0x90);
Serial.write(127);
Serial.write(100);
delay(500);
Serial.write(0x80);
Serial.write(127);
Serial.write(0);
delay(1000);
}

Serial.write()会把括号里的数字(0x表示十六进制数,0b表示二进制数,普通数字默认为十进制数)转换成二进制数再通过串口发送。要注意波特率设置需要与桥接软件里的波特率保持一致。

15.5 更多 MIDI 指令

除了开启音符(Note On)和关闭音符(Note Off)外,MIDI还有很多其他指令以丰富声音表演的细节。

复调压力(Polyphonic Key Pressure)用于音符开启后持续地改变压力值(Note On第2个数据字节只定义了音符开启瞬间的压力大小),它同样有3个字节的数据,以1010xxxx开头,后两个数据字节分别是音符和压力值。

与复调压力类似的有通道压力(Channel Pressure),通道压力以1101xxxx开头,因为控制的是整个MIDI通道的音符的压力大小,所以它后面只需要1个字节的数据:1101xxxx-0vvvvvvv。

程序变更(Program Change)通常用于改变发声设备的音源,MIDI本身只是一个控制指令,发声设备能发出不同乐器的声音是因为设备里面内置了很多不同的音源,MIDI指令只起到控制作用。程序变更以1100xxxx开头,后接单字节数据(范围从0到127,对应通用MIDI标准下128个不同的音源)。至于其他更多的指令,可以自行搜索并了解它们的作用。