【全文三万字】全网最全MIDI技术(计算机向)详解!MIDI编码原理?MIDI编程?只看这一篇就足够了!

零、引言

        该篇博文主要面向需要处理MIDI相关计算机项目或课题的同学,全面介绍了MIDI技术相关的技术及原理,对于处理MIDI编程、基于MIDI的深度学习,音乐生成、音乐表示等任务具有极高的指导意义,部分资料源于互联网收集。文章稍长,可以根据目录来进行导航。接下来,就让我们开始吧!


目录

零、引言

一、MIDI背景

1.1 产生与发展历史

1.2 MIDI工作方式

1.3 MIDI文件

二、常见MIDI Message介绍

2.1 Note On & Note Off

2.2 Mono Pressure(Aftertouch)

2.3 Program Change

2.4 Pitch Bend

2.5 Control Change

三、MIDI文件格式解析

3.1 MIDI文件结构简介

3.2 Header Chunk结构

3.3 Track Chunk结构

3.4 Delta-Time及MIDI事件结构

3.5 总结

3.5.1 MIDI文件的总体组织结构

3.5.2 Header Chunk解析

3.5.3 Track Chunk解析

3.5.4 例子

四、MIDO与MIDI原理实践(MIDI编程)

4.1 Mido库简介

4.1.1 Message

4.1.2 Meta-Message

4.2 MIDI原理实践

4.2.1 使用Mido来生成简单的MIDI乐曲文件

4.2.2 使用Mido来解析示例MIDI文件

附录

GM音色表

GM鼓乐器音色表

MIDI CC List(常用的标红)

音高编号对照表

Mido官方文档 


一、MIDI背景

        MIDI是Musical Instrument Digital Interface的缩写,直接翻译过来的意思就是乐器数字化接口,也就是说它的真正涵义是一个供不同设备进行信号传输的接口的名称。它在20世纪80年代初为解决电声乐器之间的通信问题而被提出,一直沿用至今。我们如今的MIDI音乐制作全都要靠这个接口,在这个接口之间传送的信息也就叫MIDI信息。MIDI是一种协议、一种标准、或是一种技术,而并不是某个硬件设备。

1.1 产生与发展历史

        MIDI不是首先出现在计算机里面的,它是由电子乐器生产厂家为了不同型号的电子乐器的“交流”而产生的,早期的MIDI设备除了都能接受MIDI信号之外没有统一的标准,尤其是在音色排列的方式上更是“随心所欲”的。也就是说在这台琴上制作完成的音乐拿到另一台不同型号的琴上播放时会变得面目全非,小提琴可能会变成小号,长笛可能会变成吉他,钢琴可能会变成大鼓……这对于专业音乐人士的工作并不会产生太大的影响,毕竟他们制作一次灌成唱片也就完事儿了,但是对于音乐爱好者之间的交流,尤其是多媒体的发展却极为不利。

        因此,1983年,为了统一标准,并建立起一种连接电子乐器和计算机的规则,国际乐器制造者协会的十几家厂商(主要来自美国和日本)开会通过了美国Sequential Circuits公司的Dave Smith提出的“通用合成器接口(USI)”的方案,并改名为“乐器数字接口(MIDI)”。

        1983年,MIDI协议1.0版正式问世。此后,几乎所有电子乐器的背后都出现了五孔的MIDI接口。只要通过几根MIDI线,电子乐器与电子乐器、电子乐器与电脑之间就可以实现通信。

MIDI接口​​​​​​

        值得一提的是,Dave Smith和其他一同参与制定该协议的人向全世界免费开放了MIDI协议的使用权。这一举措直接影响了未来几十年应用MIDI协议的各种硬件和软件的发展。包括我们现在能够使用和了解到这项技术,并基于这项技术进行编程、创造等,很大程度上也是受益于这一举措。

        1984年,在原有的MIDI协议之上,日本罗兰公司提出了GS(General Synthesizer标准。此标准定义了最常用的128种乐器、音效和控制器的排列。

        GS标准的内容分为五个部分:

  • 16个声部

  • 最大复音数为24或更多

  • GS格式的乐器音色排列(包含有各种不同风格的音乐所使用的乐器音色和打击乐音色)

  • 鼓音色可以通过音色改变信息进行选择

  • 两种可以调节的效果,包括混响和合唱

        1991年,国际MIDI生产者协会(MMA)以GS标准为基础,制定了通用MIDI标准 ——GM(General MIDI system Level 1)。在GS标准的基础上,GM标准规定了MIDI设备的最大同时发音数不得少于24个、鼓等打击乐器作为一组单独排列以及128种乐器音色有统一的排列方式等。GM的音色排列方式与GS标准几乎一致。GM标准的提出得到了Windows操作系统的支持,此时MIDI才算是真正意义上与计算机开始挂钩了。

        1994年,YAMAHA公司在GM标准的基础上推出了XG(Extended General MIDI)标准,增加了更多的乐器组,扩大了MIDI标准的定义范围。在兼容GM的基础上,XG标准做了大幅度的扩展,其有520种音色,32复音,3种可编辑效果,并支持多轨音频输入。

        2020年,MIDI 2.0版的协议正式发布,这个新版本的协议拥有256个MIDI通道和最高32位的解析度(MIDI 1.0只有16条通道以及8位的解析度),并且具有双向交互、向下兼容的特点。得益于此,应用了MIDI 2.0协议的不同设备之间的更为复杂的交流将成为可能,演奏时值与参数控制会更加精确、高效,错误也会因此减少。不过由于刚提出相对来讲目前还不够成熟,因此我们对MIDI的分析与研究大部分还是针对之前标准的,涉及MIDI2.0标准相对较少。

1.2 MIDI工作方式

        与大多数数字连接器一样,有一个 MIDI 端口和一条MIDI 电缆。前者有五个孔,由后者插头上的五个销子塞住。设备具有三种 MIDI 端口类型中的一种或多种:MIDI 输出、MIDI 输入和 MIDI 直通。MIDI Out 允许从设备发送信息,MIDI In 可以接收它,而 MIDI Thru 允许信息通过该设备传输到另一个设备。在每种情况下,通常都需要辅助电缆来回传输信息,而不是仅在一个方向上传输信息。

        当想要扩展 MIDI 连接时,可以使用MIDI 耦合器,它可以让使用者将两根 MIDI 电缆插入其中。或者,可以使用MIDI 分离器和 Y 型电缆。

        对于多个设备之间更复杂的 MIDI 连接,可以使用 MIDI 接口,它可以在一个中央位置将更多 MIDI 兼容设备连接在一起,从而帮助将所有信息传输到正确的乐器或设备。

        现代设备通常同时具有传统的 MIDI 和 USB 连接,这两种连接都允许相同的 MIDI 消息传输或接收。对于没有 USB 的设备,可以使用 USB 转 MIDI 适配器。有些,例如 Cable Matters 的 USB 到 MIDI 电缆,甚至支持 USB-C 设备,如平板电脑。

MIDI设备连接示意图​​

        当谈到使用多个 MIDI 连接设备创作音乐时,MIDI 序列器使过程变得更加简单。它们存储 MIDI 数据并管理其播放、录制和编辑。大多数现代数字音频工作站 (DAW) 都包含 MIDI 音序器,并且在许多情况下,在它们成为当今更现代的 DAW 接口之前就开始使用它们了。

        或者,可以使用通过 USB 连接连接到 DAW 的硬件音序器,这可以让创作者使用 VST 插件访问近乎无限的数字乐器阵列。也可以在没有计算机的情况下专门使用硬件音序器将各种支持 MIDI 的设备和乐器连接在一起。然后它们可以代替 DAW 进行编辑和作曲。

        更详细的MIDI工作方式涉及到更多的硬件知识,相比之下我们作为编程以及算法研究人员更关心MIDI的软件层次方面,因此在此不对工作方式进行更深入的介绍。

1.3 MIDI文件

        在计算机中一切都是以文件的形式来进行存储的,因此为了让MIDI与计算机相通,MIDI文件便出现了。应用MIDI标准协议创建的文件我们称之为MIDI文件。我们可以把MIDI文件理解成一种计算机也可以读懂的乐谱,只不过MIDI文件不像用文字写成的纸面文件那样有着可以直接观测到的外形,而是以计算机可以理解的底层二进制代码的形式存储在了计算机的内存和磁盘之中。

        一个标准的MIDI文件往往以二进制代码的形式包含了多层次的信息,通常以“事件(消息)”来刻画这些信息。如音符开启事件(Note On)音符力度(Velocity)音符结束事件(Note Off)触后(Aftertouch),还有弯音(Pitch Wheel)调制(Modulation)主音量(Main Volume)表情(Expression)等。在这些不同层次的信息中,绝大多数值的取值范围都在0~127,在后面也会对这些事件信息进行更深入的分析。

        在计算机系统中,MIDI文件的文件后缀往往表示成“.mid”。虽然MIDI只是一种乐谱性质的东西,但是这种文件往往可以直接用计算机上的音乐播放器打开。此时计算机所播放出的音乐是其在读取到MIDI乐谱信息后,调用自带的合成音色所奏出的。此外,一些市面现有的音乐软件也支持MIDI文件的直接播放,作为MIDI文件的使用者,我们不必去读复杂难懂的底层二进制代码就可以直接播放使用这些MIDI文件。但是作为MIDI编程人员,我们仍有必要了解MIDI文件的底层格式。因此在后续会对MIDI文件的底层格式进行详细的介绍与解析。

        创建MIDI文件一般来说有三种方式:

  • 运用连接到计算机的MIDI设备来进行输入:这类设备主要就是MIDI键盘和一些键盘合成器,此外也有一些像MIDI吉他之类的较为少见的MIDI设备。

  • 将音频文件转化成MIDI文件:这种方式一般对于音频文件的要求比较高,最好其本身就是由MIDI文件生成的,否则转换出来的MIDI文件往往都是一堆乱码,完全不能用。

  • 在一些宿主软件(DAW)中用鼠标和键盘等外设来进行输入:这也是目前来讲输入精度最高的一种MIDI创建方式,无论是用MIDI键盘还是别的MIDI设备进行输入,都不可能做到百分之百的时值和节奏的准确,但直接在宿主里面进行编辑就不存在这个问题。

        此外,对于我们编程研究人员来说,还有一种额外的不可忽略的创建MIDI文件的方式,就是MIDI编程,同样可以用高精度的方式来创建MIDI文件。这也是我们需要着重关注的一种方式,与后续研究息息相关。

二、常见MIDI Message介绍

        MIDI在乐器、计算机、控制器和其他 MIDI 设备之间发送的信号称为MIDI消息(MIDI Message)。MIDI消息是一种二进制数据,它包含了音符、音量、音色等音乐信息。

        MIDI消息分为三种类型:实时消息、系统消息和通道消息。实时消息是指在任何时候都可以发送的消息,如时钟、定位器和开始/停止消息。系统消息是指用于控制整个MIDI系统全部通道的消息,如重置、全局控制和设备控制消息。通道消息是指用于控制单个MIDI通道的消息,如音符、音量和音色消息。这些消息最终决定了不同设备如何相互交互。例如,使用调音台来控制连接的键盘的声音。在其他情况下,没有自己特定接口的声音模块可能需要连接 MIDI 控制器才能产生声音。通过MIDI消息的作用便不难发现MIDI消息是整个MIDI技术中的核心,是MIDI技术中各个部分相互连接、通信、交互的纽带。

        我们通常会把实时消息和系统消息统称为系统消息,因而将MIDI消息大体上分为系统消息和通道消息两大类。每类消息包括主要的消息类型大致如下图所示: 

MIDI消息类型

        相比于系统信息,通道信息与最终形成MIDI文件的听感息息相关,联系更为紧密,因此我们在编程研究时更为关注通道信息。接下来将对通道信息中最常用到的信息类型进行介绍。

2.1 Note On & Note Off

        NOTE ON 和 NOTE OFF 是最主要的两个 MIDI Message。当演奏者敲击音乐键盘的琴键时发送 NOTE ON 消息,它包含了音高以及“力度”的参数。当合成器收到此消息时,它会开始以相应的音高和“力度”播放该音符。当收到 NOTE OFF 消息时,合成器会终止该音符。

        每个 NOTE ON 消息都需要相应的 NOTE OFF 消息,否则该音符将一直处于播放状态。但打击乐器可以只发送 NOTE ON,因为打击乐音符会自动停止。但最好养成始终发送 NOTE OFF 的习惯,因为不同合成器对这一特性的实现可能不一样。

2.2 Mono Pressure(Aftertouch)

        触后信息指的是当音符被“触键”发生后由于触键压力的再次变化而生成改变音色特性的信息,主要在一些高级的合成器或音源中具有此功能。主要又可以分为两类:

  • 键位触后(Key Aftertouch)复音触后(Polyphonic Aftertouch):键位触后主要针对的是某些要被变化的音符,所以它的两个数据是键位压力值和音符编号。在同一个通道,有些音符可以被设置为键位触后,有些则不设置。

  • 通道触后(Channel Aftertouch):通道触后主要针对的是要被改变的某个通道,所以它的两个数据值是压力值(力度)和通道号。也就是说,可以将某1个或2个不同的通道都设为键位触后,这样每个通道的所有音符都具备触后功能。

2.3 Program Change

        音色程序改变也就是俗称的音色变化,即如何使用多个乐器的音色。音色程序的改变是由两个数值控制的:第一个数值是音色库选择(Bank Select),第二个数据则是具体的音色程序改变号(Patch Select),一些合成器利用这个数值就可以改变音色。我们通常先选取一个音色库,再从音色库中选择具体的音色所对应的音色程序改变号,这样就可以得到或者改变一个自己想要的音色,音色库和音色程序改变号分别用7位存储,这样音色就有了$$2^7*2^7=16384$$种选择,给予了我们极大的灵活性。GM音色表见附录。

        在 GM 标准下,信道 10 是保留给打击乐器的(实际上合成器可以在任何信道上使用鼓),在这个信道上乐器编码遵循通用 MIDI 鼓乐器列表,列表见附录。

2.4 Pitch Bend

        MIDI支持以弯音信息的方式来模拟合成器中弯音轮的作用。弯音轮最早是合成器上的一个控件,用于以连续可变的形式改变音高,也就是滑音表情。一般 MIDI键盘的弯音默认范围为 ±2 个半音。弯音就是一个乐音被向上弯至不同的音高,或向下弯至另一个不同的音高,向上或向下的弯音的音高均不确定,整个音高变化的过程是连续的,而非通常的离散音符的切换。弯音信息的存在给予了MIDI音乐更高的质量、更多的可能性,给MIDI音乐的创作者留足了空间。

2.5 Control Change

        MIDI 设备通常会提供一些控制器用于改变合成器的某个参数,比如混响、增益等。MIDI 协议可以使用控制器消息操作 128 个不同的控制器。控制器消息一方面可以用于改变合成器的某些参数,另一方面,控制器编码可以通过“组合”的方式实现一些更复杂的指令。在 MIDI 中控制器消息和音源与效果器的参数密切相关,不同编号的控制器有一些约定俗成的含义,在程序中实现控制器时需要尽量与已有的规范对齐,目前规定的控制器参数详细规范见附录。

        至此,我们已经对所有常用的MIDI Channel Message都进行了简单的介绍,光这样对他们的功能进行简单的概括并不能让我们完全理解这些消息的作用与含义,因此,接下来,我们便从最底层的二进制代码出发,来对MIDI进行详细透彻的解析,在解析的过程中也同样会有助于我们对这些消息的理解。前面也有提到,MIDI消息是整个MIDI技术中的核心,理解上面提及到的这些消息是极其有必要的。

三、MIDI文件格式解析

3.1 MIDI文件结构简介

        MIDI文件是二进制文件,其内部主要记录了乐曲播放时,音序器应发送给音源的MIDI指令和每条指令发送的时间点。音序器读取这些时间信息和MIDI指令,通过在相应的时间发送相应的指令,以实现乐曲中音符的顺序播放和节拍信息。除了音序器需要发送的MIDI事件之外,MIDI文件内部也记录了一些辅助信息,如版权信息、音轨名、速度信息、拍号、调号等等,这些信息被称为Meta-event,只用于记录一些曲子的信息,通常并不发送给MIDI系统中的其他设备。

        MIDI文件的数据结构被称为“chunk”。每个chunk由最初4字节的“chunk类型”,紧接着4字节的“Chunk大小”,和最后长度可变的“Chunk Data”构成。“Chunk data”的数据长度由“Chunk大小”来规定,即“Chunk大小”只描述了chunk中数据段的长度,而不是整个chunk的长度。

        构成MIDI文件的Chunk主要有两种类型:一种是Header Chunk(MThd),另一种是Track Chunk(MTrk)。

        Header Chunk位于整个MIDI文件的起始处,是必须存在的,其起始标记就是ASCII码形式的“MThd”字符串。Track Chunk的起始标记,依然是ASCII码形式的“MTrk”字符串,并且Track Chunk整块分布于MIDI文件之中的任何位置,数量也不定,从1块到若干块皆可。实际上一个MIDI文件就是由一个Header Chunk和若干Track Chunk组成。使用十六进制编辑软件打开并查看一个MID文件时,便能找到这两部分。

MIDI文件的十六进制编码格式示例

        MIDI文件可能容纳的chunks只有Header Chunk和Track Chunk,其它的非法数据结构将被忽略。在MIDI的Chunk文件结构中,自长度区以后的数据格式,是严格规定好的。

        这里要探讨不同数量的MTrk chunk所构成的MIDI文件的类型。MIDI文件的类型通常分为三种,分别是MIDI 0格式文件、MIDI 1格式文件、MIDI 格式2文件。它们的相同点是:无论哪一种格式的MIDI文件,都要具备一个MThd chunk,和至少一条MTrk chunk。不同点是:它们各自的MTrk chunk数量不同,并且各个 chunk之间的播放方式略有区别。很多入门级电子琴和具备播放简单和弦铃声的手机,只能播放MIDI 0格式文件;平时用音序器软件编辑MIDI时,又最好保存为MIDI 1格式文件。这种现象是有原因的。

        MIDI 0格式文件只有一个MTrk chunk。在这个 chunk中,包含了整个MIDI文件中的MIDI事件,包括meta-event、演奏信息、效果器信息等等。所以播放器只需要顺序读取并解析文件,并发送实际的MIDI事件即可。播放MIDI 0格式文件,对于入门级电子琴或者手机这种性能较弱、资源紧张的嵌入式系统来说,相对容易一些。音序器不需要考虑在不同MTrk之间来回跳跃取数,只需要像流媒体文件那样顺序读取并解析就行了。

        MIDI 1格式文件具有若干条MTrk chunk,并且chunk之间具有统一的时间信息,也就是说,各个chunk之间的播放是同步进行的。MIDI 1格式文件的第一条MTrk chunk是专用的,称为“Tempo Map”。它包括整个MIDI文件中所有的 meta-event。从第二条MTrk chunk开始,每一条MTrk chunk都记录着各自的演奏和效果器等信息。音序器在播放时,将使用统一的时间等信息,同步播放各个chunk。这就像cakewalk软件播放MIDI文件一样,在一个时间轴的滚动下(音轨区的那条标志播放位置的竖线),各个音轨同时播放。实际上cakewalk软件也仅支持MIDI 0和MIDI 1文件。

        MIDI 2格式文件也具有若干条MTrk chunk,但每个chunk具有独立的时间信息,也就是说各个chunk的播放并不是同步的,而是每个chunk都遵循自己的时间信息,chunk之间没有统一的时间联系,各自播放。这种格式的文件目前较少,最常见的还是MIDI 0格式和MIDI 1格式的文件。

        用一张表总结如下:

        综上所述,在解读MIDI文件时,首先要找到各个块,也就是一个MThd chunk和若干MTrk chunk的ASCII字符串。这样才能根据MThd和MTrk中所记录的信息,确定此MIDI文件的基本参数,并进行下一步更详细的解析。关于MThd和MTrk中详细的解析规则,将在下文中具体进行介绍。

3.2 Header Chunk结构

        MThd chunk中保存了此MIDI的一些基本信息,如文件格式(MIDI 0、1、2格式)、此MIDI文件的音轨数(从1到多条)、时间类型(使用MIDI Tick或Frame来计时)。作为一种既定的标准,MThd Chunk一定是类似这样的数据结构(十六进制):

     4D 54 68 64      // ①MThd的ASCII码,为Header Chunk的标志
     00 00 00 06      // ②MThd中数据部分的长度,以目前标准均为6字节
     hh hh            // ③MIDI文件类型
     ii ii            // ④此MIDI文件的音轨数目
     jj jj            // ⑤此MIDI文件的时间类型 

        Header Chunk之中的数据结构定义是严格遵循这个标准的。目前的标准中并未规定Header Chunk有其他的数据定义(但以后也许会扩充)。所以说,在Header Chunk中,前八个字节(即①②的数据结构)是固定样式的,数据段的大小也是固定为6字节的。

      下面对数据段中的具体数据结构作一个介绍:

      数据③标志着该MIDI文件的格式。MIDI文件格式有三种,0、1、2格式,所以可以分别用0000、0001、0002来表示。每种格式的具体含义请见上文。

      数据④标志着该MIDI文件中所包含的音轨数目,也可以认为Track Chunk的数目。对于MIDI 0格式文件,此值仅为1,即只有一个Track Chunk;MIDI 1格式文件则可以有多个Track Chunk,而且Track Chunk数目为实际的音轨数目加一,因为第一个Track Chunk是Tempo Map,不记录实际的演奏信息,Tempo Map多用来记录MIDI文件信息而不包含音符信息,比如作曲者、歌曲名称、版权等。MIDI 2格式文件数量较少,在此不作解释。

      数据⑤标志着该MIDI文件的时间类型。MIDI的时间类型通常有两种,一种是基于TPQN(Ticks Per Quarter-Note,每四分音符所具有的Midi Tick数)的时间度量法,另一种是基于SMPTE时间码的时间度量法。在这里,MIDI文件使用这个十六位数的最高位,标志这两种时间类型。也就是说,这个时间类型如果大于0x8000,则为SMPTE时间码度量法;如果小于0x8000,则为TPQN时间度量法。而此数的后十五位,则记录着具体的Midi Tick数量。

      SMPTE本来是用于视频中的协议,所以它的计量单位为“帧”,就是“frame”。视频中有“帧率”的概念,单位为“帧/秒(fps)”。不同的视频标准中有不同的帧率,比如25fps、30fps等等。如果MIDI系统中使用这种时间度量法,那么它所定义的就是,在每一帧中,所具有的Midi Tick数目。其编码规则简单说就是使用了 SMPTE 时间码的规范。其 14 - 8 位包含了包含 -24、-25、-29 或 -30 四个值之一,对应于四种标准 SMPTE 时间码格式(-29 对应于 30 个丢帧),并表示每秒的帧数。第 7 到 0 位表示帧内分辨率。这种度量法在单纯的MIDI系统中比较少见,故不细谈。

        同样用一张表格总结如下:

        对于大多数只有音频的MIDI系统中,MIDI文件多采用TPQN时间度量法。TPQN是“Ticks Per Quarter-Note(每四分音符中所包含的Midi Tick数量)”的缩写,它的意思可以从字面来理解。这个数值可以是十进制的60-480之间,数值越大,MIDI系统的时间分辨率就越大,也就是说可以演奏时值越小的音符。通常这个数都采用120、240、480,因为这些数都能被2、3、4甚至6、8整除,方便于八分音符、十六分音符、三连音甚至更短音符的演奏,换算成十六进制,就是0x78、0xF0、0x1E0。当然注意,这些十六进制数的最高位都是0。

MIDI文件举例

        举个例子如上图所示,这是一个MIDI文件的开头部分,根据我们上文的分析结果可以轻易地推断出该MIDI文件为MIDI 1格式文件,音轨数为0x000B=11轨,其中1tempo轨 10分轨,时间类型部分为0x1E0,小于0x8000,因此为TOQN时间度量法,值为0x1E0,即480,上文也有提到,是常见的ticks数之一。

3.3 Track Chunk结构

        Track Chunk内部则包含了一个MIDI文件中记录的实际的MIDI信息和一些辅助信息(如meta-event)。

        Track Chunk依然具有和Header Chunk类似的结构,就是“Chunk标志”+“数据段大小”+“数据”。所以它的结构如下所示(十六进制):

     4D 54 72 6B      // ①MTrk的ASCII码,为Track Chunk的标志
     pp pp pp pp      // ②MTrk中数据部分的长度
     xx yy            // ③Delta-time及MIDI事件
     xx yy            // ③Delta-time及MIDI事件
     ……               
     00 FF 2F 00      // ④meta-event事件,此Track结束 

        数据①依然是Chunk标志,只不过该标志被换成了ASCII码的MTrk,代表接下来的数据为Track Chunk的数据。

        数据②依然是此Chunk中所包含数据的大小。当然这个数就不是如同Header Chunk中那样的常数了,而是要精确描述接下来Track Chunk数据段的大小了。

        接下来就是Track Chunk中所包含的真正数据了,就是由许多类似③那样的数据堆积起来的大段数据。xx代表了Delta-time,yy代表了真正的MIDI事件。这些MIDI事件才是音序器在播放MIDI文件时需要实时处理和发送的数据。在下文中将会对事件的格式进行详细介绍。

        数据④从严格意义上讲,也属于③的类型。最初的00代表delta-time,随后的FF 2F 00为一段meta-event,代表了本Track结束。

3.4 Delta-Time及MIDI事件结构

        我们把MTrk存储的数据单元称为事件,事件由Delta-time和MIDI事件两部分组成。

        Delta-time,实际上就是“Δt”,它代表着时间差。MIDI系统中的delta-time,表征着下一个事件距离上一个事件有多长时间,即两个事件之间的时间差。这个时间不是我们日常生活中的时分秒,而是MIDI Tick。音序器通过对自身产生的MIDI Tick进行计数,判断是否该处理下一个MIDI事件。如果Tick数到达delta-time,就处理下一个事件,然后继续判断下一个delta-time是否到达,周而复始。为了能够表示足够长的时间,Delta-time使用动态可变长度数的格式。

        动态字节:只使用字节的低七位用来存储数据。如果要存储127这个数字,只会使用一个字节的低七位,也就是01111111(7F) 。如果要存储128,则需要使用八个字节。这个时候,会使用字节的最高位,把它标记成1,代表这个字节和下一个字节合并起来存储数据,因此128将会使用两个字节,存储成 10000001 00000000 (81 00)

        对于更大的数字,除了最后一个字节的最高位是0,前面的字节的最高位都需要记作1用来表示它和后一个字节合并。

      以上的表述可能略显抽象,光通过以上的表述我们可能无法具体地了解动态字节的表示方式,接下来便举两个例子来具体地分析一下,假设我们要将240转换为动态字节码表示,也就是MIDI文件中的表示:

        上图就可以很清晰明确地展示出动态字节码的原理了。

        MIDI事件则包括实际需要发送出去的MIDI事件,和meta-event事件。对于实际需要发送的数据,音序器就直接将数据发送出去;如果是meta-event事件,音序器则修改自身的相关参数。MIDI事件由事件标记和事件数据两个部分组成。存储事件标记的字节的最高位一定是1,存储事件数据的字节的最高位一定是0。这一点在后文谈到的事件缩写中会起到作用。

        事件标记:一个字节,用于说明事件的类型,具体情况如下表,x为低四位,代表着事件需要作用于的Channel编号:

        可以看到,表中大部分的事件我们在第二章之中都已经详细介绍过了。在这里我们又从底层二进制代码的角度来对这些MIDI Message重新进行了分析。通过这样的分析,我们对MIDI Message的理解也应更近一步了。

        接下来具体分析一下8x和9x即“Note-On”和“Note-Off”这两个和音符有关的事件,这两个是MIDI中最常用的事件,如果只是想要粗略地演奏midi文件,只需要这两个事件数据就可以了。 对于这两个事件的两个字节事件数据,第一个字节是音高,第二个字节是力度。 音高的数字大小为0~127,其中数字大小60对应着钢琴里的中央音符C3。在此基础上音高每+1,就代表对应的钢琴键右移一位,每+12代表升高一个八度。 音高编号对照表见附录。力度编号对照表大致如下表所示:

力度编号对照表

        当事件标记符为FF时,则标志着这个事件为meta-event事件,后文也会对几种常见的meta-event事件进行介绍。

        当事件标记符为F0时,则标志着这个事件为system事件,F7表示系统独有消息结束。当合成器监听到F0时,检查下一个字节 0iii iiiiiii iiii 是一个 7 位的制造商 ID。如果合成器识别出这个代码则会继续监听后面的数据,否则则忽略掉收到的消息,直到结束消息 1111 0111 即F7结束标识符出现。

        值得注意的是表中的最后一行是00~7F,也就是字节最高位为0。这种情况就是MIDI文件的“状态字省略”特点。为了减少MIDI文件的体积,人们规定:如果同一Track Chunk中的下一条MIDI事件,和上一条事件,属于同一类型同一通道的事件(即状态字相同)时,下一条事件的状态字可以省略,而只需记录数据。音序器碰到这种情况时,应自动填充上一事件的状态字。

      举个状态字省略的例子,MIDI文件中的事件,大多数都是“Note-On”和“Note-Off”事件,其中“Note-Off”事件也可以用“Note-On”+“力度为0”来表示。所以在MIDI文件中,这种缩略形式会用到很多。比如连续演奏几个音符时,MIDI文件就会使用这种缩略法来减少文件体积。在MIDI文件中,常常会看到这种序列(十六进制):

     00 93 3C 6B      // 音符0x3C,Note-On,力度为0x6B
     70 3C 00         // 实际发送的指令为隔0x70 Ticks之后,发送93 3C 00

        因为力度为0,也就相当于让某个音符停止发音。83 3C 6B和93 3C 00的效果是一样的。所以通过这种写法,MIDI文件可以省略掉一个字节的空间。

3.5 总结

3.5.1 MIDI文件的总体组织结构

MThd <length of header data>
<header data>

MTrk <length of track data>
<track data>
MTrk <length of track data>
<track data>
. . .

3.5.2 Header Chunk解析

<Header Chunk> = <chunk type><length><format><ntrks><division>
Header Chunk解析

        上面的表格直观地总结了我们之前对文件头分析的结果,只要能看得懂英文,便可以很轻易地看懂上面的表格。

3.5.3 Track Chunk解析

Track Chunk解析

        上面的表格中所描述的结构我们现在同样是可以轻易理解了的。值得多提一句的就是其中的一些常见的meta-event的具体结构:

常见meta-event结构

         相对更全面的表格如下:

3.5.4 例子

        最后,举两个例子来对以上的分析进行一个简单的总结:

MIDI 0格式文件 单轨
MIDI 1格式文件 多轨 且第一轨为Tempo Map

四、MIDO与MIDI原理实践(MIDI编程)

        通过前三章的详细介绍,我们已经对MIDI的原理部分有了一个很深入的理解了,接下来,便是从编程角度来讨论MIDI编程相关,作为一名编程研究人员,我们该如何应用这些MIDI理论来进行实践,把我们的理论知识转换为实际上的研究成果与输出呢,并为后续的研究提供支持呢?

        首先,基于对以上MIDI理论的理解,我们完全有足够的能力来写一个MIDI文件分析的程序,程序读取MIDI文件的二进制代码,然后模拟我们在前文中对MIDI文件二进制代码解析的过程,来分析出MIDI文件中含有的各种信息,然后把这些信息以一种直观的方式(可以是乐谱,可以是钢琴卷编码,或者是更简单的文本信息的方式)返回给用户。但实际上,市面上已经有很多软件实现了这样的功能,而且实现得十分全面且完善,如库乐队、MuseScore等,都可以让用户十分方便地把MIDI文件转换为易懂的乐谱、文本信息等。我们在研究的过程中无需重新实现这些已经存在的功能,只需要使用这些软件,作为研究过程中很好的工具即可。

        换而言之,这样的一个MIDI文件分析的程序并不会给我们未来的研究带来多少帮助。那作为编程研究人员,我们到底需要的是什么呢?我们需要的是一种“代码直观”的分析程序或者分析库,相比于把MIDI文件转换为过于具体的乐谱、钢琴卷,我们更希望能有一个工具来读取MIDI文件中的信息,然后把这些信息以变量或者对象的方式返回给我们,这样我们以后在使用这些信息,做MIDI编码等工作的时候将会感受到极大的便利。此外,如果可能的话,我们也希望这样的一个工具能够支持我们不用操作底层二进制代码,只调用一些上层的函数,就可以方便地进行MIDI编程,这对我们未来的研究也将会有极大的帮助。总结来看,我们更需要封装一个好用的MIDI函数库,既能调用其中的函数,以“代码直观”的方式读取解析MIDI文件并返回MIDI消息对象,也能让我们通过调用其中的函数来操作MIDI底层二进制代码,从而方便地实现MIDI编程。

        对于封装这样的一个库的工作,我们显然可以亲力亲为,这难度并不大,但在造“轮子”之前,我更倾向于寻找目前是否已经存在了这样的“轮子”呢?如果已经存在的话,我们只需要学习使用就可以了。这又可以简化我们的工作,提高我们的研究效率。幸运的是,我成功找到了这样的库,即Python中的Mido库。因此,下文将会对Mido库来进行简单的介绍,并使用Mido库来进行简单的实践。我们上文中介绍的各种MIDI理论能够极大程度上加快我们对Mido库的理解,同时也能降低理解难度,而我们使用Mido库进行的实践也能反过来印证我们之前的理论,这是一个双向促进双向成全的过程。因此这样的简单实践还是十分有必要的。可以说,在掌握了Mido库的使用并使用Mido库进行实践,成功对我们之前的理论进行印证了之后,作为一名MIDI相关课题的研究人员与编程人员,我们前置所需要的理论知识与技术基础才相对完善与过关了。

4.1 Mido库简介

        Mido 是 Python 中用于处理MIDI音乐文件的库。Mido库使得在Python中处理MIDI文件变得容易,并提供了创建、解析、编辑和生成MIDI数据的功能。通过Mido库,我们可以轻易实现以下我们理想中的MIDI相关功能:

  1. 读取和解析MIDI文件:Mido 可以用来读取和解析现有的MIDI文件,以便在Python中分析和编辑它们。

  2. 创建和生成MIDI文件:可以使用 Mido 来创建新的 MIDI 文件,包括音符、控制器信息和其他 MIDI 事件。

  3. MIDI事件处理:Mido 提供了一个简便的方式来处理 MIDI 事件,包括音符、控制器、程序更改等。

  4. 与硬件 MIDI 设备通信:Mido 也可以与硬件 MIDI 设备进行通信,例如 MIDI 控制器或合成器。这对于将 Python 应用程序与音乐硬件连接起来非常有用。

  5. 时间轴管理:Mido 可以管理时间轴,以确保 MIDI 事件按照正确的时间顺序发生。

  6. 跨平台:Mido 是一个跨平台的库,可以在多个操作系统上运行。

        其中前三点是我们尤为关注的,我们在未来与MIDI文件打交道时,会很频繁地使用Mido提供给我们的前三项功能。接下来便对Mido库的具体用法进行简单介绍,在此仅介绍最为常用的函数,更多、更全面的函数用法可以参照Mido官方文档。

        实际上,官方文档上的一个示例代码直接体现出了Mido操作MIDI文件的核心思想以及最常用到的核心函数:

from mido import Message, MidiFile, MidiTrack

mid = MidiFile()
track = MidiTrack()
mid.tracks.append(track)

track.append(Message('program_change', program=12, time=0))
track.append(Message('note_on', note=64, velocity=64, time=32))
track.append(Message('note_off', note=64, velocity=127, time=32))

mid.save('new_song.mid')

        代码虽短,但能充分地体现出Mido操作MIDI文件的基本思路:

  • 首先创建一个MidiFile对象,作为MIDI文件的载体

  • 创建一个(或多个)MidiTrack对象,作为MIDI文件的音轨,将其append到MidiFile中

  • 向一个(或多个)MidiTrack对象内添加Message对象(包括program_change、note_on、note_off等)和MetaMessage对象(用以表示MIDI文件的节拍、速度、调式等属性),这与我们之前介绍到的理论思想是符合的

  • 保存MidiFile对象

        示例中所用到的函数极为简单,看一眼便知道其作用,但是这几个函数却是却是极为重要且核心的。对这几个函数不做详细的介绍,同时,也同样地不再多对Mido库中提供的其他函数进行介绍,都很简单,参照Mido官方文档即可。相比之下,我更关注之前的MIDI理论知识在Mido中的体现形式,换句话说,介绍Mido库中的MIDI消息表示形式是更为重要且核心的。显然,我们也要分为普通message和meta-message来分别介绍。

4.1.1 Message

        在此对三种主要的消息在Mido中的表示进行介绍:

  • Note-on:

Message('note_on', note, velocity, time, channel)

        其中note代表音符的高低,velocity代表音强,time是delta-time,单位是微秒(ms),channel代表通道的编号。不难发现,弄懂之前的MIDI理论之后,对Mido库的理解是极其简单的,基本上库中所应用到的参数名称与理论介绍中所使用的名字是相同的。因此,对这些Mido中Message的表示也可以参照之前的理论进行理解。简单介绍几个Message,就可以通过类比来学习其他Mido中提供的所有MIDI Message。

  • Note-off:

Message('note_off', note, velocity, time, channel)

        note-off参数与note_on消息保持一致,否则有可能不能成功写入,其他参数的意义与格式也同样与note_on保持一致。

  • Program-change:

        program_change用于更改不同channel的乐器音色:

Message('program_change', channel, program, time=0)

        channel为信道编号,program为乐器编号,这里的乐器编号与GM通用标准中的乐器编号保持一致,可以参照附录中的GM音色表来找到想要使用的乐器音色。

        Mido中还提供了其他很多类型的Message,如下表所示:

Mido Message

                 其中的参数类型:

Mido Message 参数类型

         在此并不对这些信息进行一一介绍,类比学习理解即可。

4.1.2 Meta-Message

        Meta-Message的种类同样也很多,其中常用的几种Meta-Message如下:

    meta_time = MetaMessage('time_signature', numerator=3, denominator=4)
    meta_tempo = MetaMessage('set_tempo', tempo = tempo, time=0)
    meta_tone = MetaMessage('key_signature', key='C')

        其中time_signature是对于节拍的表示,在此处即3/4,参数以分子和分母来命名,set_tempo是用于设置音乐的节奏快慢,tempo代表乐曲的速度,time依旧为delta-time,key_signature则用来设置音乐调式,在此处设置为C大调,若是小调的话仅需在后面添加小写字母m,如Cm表示C小调。

        剩下的Meta-Message同样可以来类比学习理解,详情如下表所示:

        至此,便完成了对Mido库最简单基础的介绍。并没有很详细地对这些知识进行介绍,是因为首先库中的函数确实很简单,去读官方文档可以很轻易地看懂,其次,只要掌握了之前所分析的MIDI理论知识,就可以很轻易地看懂Mido官方文档中大部分的内容,最常用的函数在前文已经介绍过了,剩下的函数即用即查即学即可,已经掌握了核心思想,剩下的Mido运用推进工作便都是顺水推舟的了。最后,最好理解函数库的方式便是使用,在下文的MIDI原理实践中,我们会通过大量对Mido库的使用来极大程度上增强我们对Mido的掌握、理解与熟练程度。

4.2 MIDI原理实践

        这一部分主要有两个目的:增强我们对Mido的掌握、理解、熟练程度与使用Mido来验证我们之前的理论知识。通过两个实践来分别达成以上两个目的:使用Mido来生成简单的MIDI文件来达到第一个目的,使用Mido来解析示例MIDI文件并将解析结果与我们直接阅读其二进制底层代码分析得到的结果做对比来达到第二个目的。这两个实践实际上也可以看作是互逆的过程,覆盖了Mido库最常用的使用情况。这两个目的均达到之后,我们对Mido库的掌握便可以说是合格了,进一步地,我们作为一名入门MIDI相关项目的研究编程人员便也可以说是合格的了。

4.2.1 使用Mido来生成简单的MIDI乐曲文件

        为了尽量全面地使用到MIDI中不同消息类型,考虑编写一个3条轨道,3个信道的简单版“小星星”的一小节,其中三条轨道分别为吉他,Bass和打击乐(架子鼓),代码如下:

from mido import Message, MidiFile, MidiTrack,MetaMessage
#创建MIDI文件
mid = MidiFile()
#创建三条轨道并将其加入MIDI文件中
track_1 = MidiTrack()
mid.tracks.append(track_1)
track_2 = MidiTrack()
mid.tracks.append(track_2)
track_3 = MidiTrack()
mid.tracks.append(track_3)

#向MIDI文件中添加一些元信心,如调式、速度、拍子等
def init_midi(track,bpm,numerator,denominator,key):
    tempo = mido.bpm2tempo(bpm)
    meta_time = MetaMessage('time_signature', numerator=numerator, denominator=denominator)
    meta_tempo = MetaMessage('set_tempo', tempo = tempo, time=0)
    meta_tone = MetaMessage('key_signature', key=key)
    track.append(meta_time)
    track.append(meta_tempo)
    track.append(meta_tone)

#速度120 44拍 C大调    
init_midi(track_1,120,4,4,'C')


#将具体的音符信息添加到MIDI文件中的函数
def play_note(note, length, track,base_num=0, delay=0, velocity=1.0, channel=0):
   meta_time = 480
   major_notes = [0, 2, 2, 1, 2, 2, 2, 1]
   base_note = 60
   track.append(Message('note_on', note=base_note + base_num*12 + sum(major_notes[0:note]), velocity=round(64*velocity), time=round(delay*meta_time), channel=channel))
   track.append(Message('note_off', note=base_note + base_num*12 + sum(major_notes[0:note]), velocity=round(64*velocity), time=round(meta_time*length), channel=channel))

#添加音符 1144556 C5
play_note(0,1,track_1,1)
play_note(0,1,track_1,1)
play_note(4,1,track_1,1)
play_note(4,1,track_1,1)
play_note(5,1,track_1,1)
play_note(5,1,track_1,1)
play_note(4,2,track_1,1)

#切换Bass轨,1144556 C4 Channel 1
track_2.append(Message('program_change',channel=1,program=33,time=0))
play_note(0,1,track_2,-1,channel=1)
play_note(0,1,track_2,-1,channel=1)
play_note(4,1,track_2,-1,channel=1)
play_note(4,1,track_2,-1,channel=1)
play_note(5,1,track_2,-1,channel=1)
play_note(5,1,track_2,-1,channel=1)
play_note(4,2,track_2,-1,channel=1)

#鼓组 使用打击乐默认的Channel 10 在Mido中由于编号从0开始计数,因此Channel=9
track_3.append(Message('note_on',channel=9,note=42,velocity=64,time=0))
track_3.append(Message('note_on',channel=9,note=36,velocity=64,time=0))
track_3.append(Message('note_off',channel=9,note=36,velocity=64,time=240))
track_3.append(Message('note_off',channel=9,note=42,velocity=64,time=240))

track_3.append(Message('note_on',channel=9,note=42,velocity=64,time=0))
track_3.append(Message('note_off',channel=9,note=42,velocity=64,time=480))

track_3.append(Message('note_on',channel=9,note=42,velocity=64,time=0))
track_3.append(Message('note_on',channel=9,note=38,velocity=64,time=0))
track_3.append(Message('note_off',channel=9,note=38,velocity=64,time=240))
track_3.append(Message('note_off',channel=9,note=42,velocity=64,time=240))

track_3.append(Message('note_on',channel=9,note=42,velocity=64,time=0))
track_3.append(Message('note_off',channel=9,note=42,velocity=64,time=480))


track_3.append(Message('note_on',channel=9,note=42,velocity=64,time=0))
track_3.append(Message('note_on',channel=9,note=36,velocity=64,time=0))
track_3.append(Message('note_off',channel=9,note=36,velocity=64,time=240))
track_3.append(Message('note_off',channel=9,note=42,velocity=64,time=240))

track_3.append(Message('note_on',channel=9,note=42,velocity=64,time=0))
track_3.append(Message('note_off',channel=9,note=42,velocity=64,time=480))

track_3.append(Message('note_on',channel=9,note=42,velocity=64,time=0))
track_3.append(Message('note_on',channel=9,note=38,velocity=64,time=0))
track_3.append(Message('note_off',channel=9,note=38,velocity=64,time=240))
track_3.append(Message('note_off',channel=9,note=42,velocity=64,time=240))

track_3.append(Message('note_on',channel=9,note=42,velocity=64,time=0))
track_3.append(Message('note_off',channel=9,note=42,velocity=64,time=480))

#保存创建的文件
mid.save('test_song.mid')

        打开我们通过编程来生成的test_song.mid:

        可以看到,与我们预期生成的MIDI文件完全一致,播放之后听感也与预期完全一致。我们成功地使用Mido库避开了操作底层二进制代码来完成了MIDI编程。

4.2.2 使用Mido来解析示例MIDI文件

        Mido库的存在使得我们对MIDI文件的解析变得十分简单且直观,以我们在4.2.1中生成的test_song.mid为例,对其进行解析:

mid = mido.MidiFile("test_song.mid")
for i, track in enumerate(mid.tracks):#enumerate():创建索引序列,索引初始为0
    print('Track {}: {}'.format(i, track.name))
    for msg in track:#每个音轨的消息遍历
        print(msg)

        解析结果如下:

        解析结果与我们编程时所使用到的信息几乎完全一致,此外,Mido库还自动为我们添加上了轨道结束的Meta-Message。不过这样符合期待的结果显然是必然的,因为4.2.1和4.2.2本质上就是使用同一种工具来进行的互逆操作,那结果相符也就是意料之中的了。相比之下,更为重要的是我们直接去查看test_song.mid的底层二进制代码并使用之前的MIDI理论来“手动”地对此文件进行解析,如果解析结果仍然相同,那才是一个振奋人心的结果,它既能证明我们之前理论的正确,也能证明使用Mido来操作MIDI文件的可行,更能说明MIDI作为一种协议标准的通用性与泛用性。只有这些得到证明,我们之后基于这些基础理论与技术的研究才会有意义,才能继续顺畅进行下去。

        test_song.mid二进制代码原文件:

4d54 6864 0000 0006 0001 0003 01e0 4d54
726b 0000 0058 00ff 5804 0402 1808 00ff
5103 07a1 2000 ff59 0200 0000 9048 4083
6080 4840 0090 4840 8360 8048 4000 904d
4083 6080 4d40 0090 4d40 8360 804d 4000
904f 4083 6080 4f40 0090 4f40 8360 804f
4000 904d 4087 4080 4d40 00ff 2f00 4d54
726b 0000 0046 00c1 2100 9130 4083 6081
3040 0091 3040 8360 8130 4000 9135 4083
6081 3540 0091 3540 8360 8135 4000 9137
4083 6081 3740 0091 3740 8360 8137 4000
9135 4087 4081 3540 00ff 2f00 4d54 726b
0000 0068 0099 2a40 0024 4081 7089 2440
8170 2a40 0099 2a40 8360 892a 4000 992a
4000 2640 8170 8926 4081 702a 4000 992a
4083 6089 2a40 0099 2a40 0024 4081 7089
2440 8170 2a40 0099 2a40 8360 892a 4000
992a 4000 2640 8170 8926 4081 702a 4000
992a 4083 6089 2a40 00ff 2f00 

         添加对原文件的解析注释:

< Header Chunk >
4d54 6864   //Mthd
0000 0006   //固定
0001        //文件类型 MIDI1
0003        //三条音轨
01e0        //时间类型 小于0x8000 TPQN 480ticks一个四分音符

< Track Chunk >
<Track 1>
4d54 726b   //MTrk
0000 0058   //数据部分长度 0x58=88字节
00 ff 5804 0402 1808 
//Delta-Time=0x00=0 Time Signature numerator=0x04=4 denominator=2*0x02=4 
//0x18=24 clocks per click 0x08=8个32分音符在一个四分音符中
00 ff 5103 07a120 //Delta-Time=0x00=0 tempo=0x07a120=500000ms 换算成bpm=120
00 ff 59 02 00 00 //Delta-Time=0x00=0 调号 C大调
00 90 48 40  //Delta-Time=0x00=0 Note-On Channel 0 音符0x48=72=C5 力度=0x40=64
8360 80 48 40 //Note-Off Channel 0 音符0x48=72=C5 力度=0x40=64
//Delta-Time为动态字节码表示的0x8360=二进制(10000011 01100000) 分别去掉两个字节的最高位
//剩下的数据部分拼起来 即为二进制的111100000=480 所以Delta-Time=480
00 90 48 40  //Delta-Time=0x00=0 Note-On Channel 0 音符0x48=72=C5 力度=0x40=64
8360 80 48 40 //Delta-Time=480 Note-Off Channel 0 音符0x48=72=C5 力度=0x40=64
00 90 4d 40 //Delta-Time=0x00=0 Note-On Channel 0 音符0x4d=77=F5 力度=0x40=64
8360 80 4d 40 //Delta-Time=480 Note-Off Channel 0 音符0x4d=77=F5 力度=0x40=64
00 90 4d 40 //Delta-Time=0x00=0 Note-On Channel 0 音符0x4d=77=F5 力度=0x40=64
8360 80 4d 40 //Delta-Time=480 Note-Off Channel 0 音符0x4d=77=F5 力度=0x40=64
00 90 4f 40 //Delta-Time=0x00=0 Note-On Channel 0 音符0x4f=79=G5 力度=0x40=64
8360 80 4f 40 //Delta-Time=480 Note-Off Channel 0 音符0x4f=79=G5 力度=0x40=64
00 90 4f 40 //Delta-Time=0x00=0 Note-On Channel 0 音符0x4f=79=G5 力度=0x40=64
8360 80 4f 40 //Delta-Time=480 Note-Off Channel 0 音符0x4f=79=G5 力度=0x40=64
00 90 4d 40 //Delta-Time=0x00=0 Note-On Channel 0 音符0x4d=77=F5 力度=0x40=64
8740 80 4d 40 //Delta-Time=960 Note-Off Channel 0 音符0x4d=77=F5 力度=0x40=64
00ff 2f00   //Track结束

< Track 2 >
4d54 726b //MTrk
0000 0046 //数据部分长度 0x46=70字节
00c1 21 //program-change 改变乐器音色 改变乐器为0x21=33 即Bass
//剩余部分与Track1同理
00 9130 4083 6081
3040 0091 3040 8360 8130 
4000 9135 4083 6081 3540 
0091 3540 8360 8135 4000 
9137 4083 6081 3740 0091 
3740 8360 8137 4000 9135 
4087 4081 3540 00ff 2f00

< Track 3 >
//剩余部分与Track1同理
4d54 726b
0000 0068 
0099 2a40 0024 4081 7089 2440
8170 2a40 0099 2a40 8360 892a 
4000 992a 4000 2640 8170 8926 
4081 702a 4000 992a 4083 6089 
2a40 0099 2a40 0024 4081 7089
2440 8170 2a40 0099 2a40 8360 
892a 4000 992a 4000 2640 8170 
8926 4081 702a 4000 992a 4083 
6089 2a40 00ff 2f00 

        可以看到,我们的结论与Mido解析出来的结果仍然是完全一致的。

        至此,我们完成了对MIDI的全方面分析与介绍,有了这样的基础,我们接下来针对MIDI相关的后续研究,如MIDI编码,MIDI乐谱对齐,开发基于MIDI的自动编曲系统等就可以继续顺利进行了。

附录

GM音色表

GM鼓乐器音色表

17 Voice One人声“One” 18 Voice Two人声“Two 19 Voice Three人声“Three” 22 MC-505 Beep 1 MC-505信号音1 23 MC-505 Beep 2 MC-505信号音2 24 Concert SD大乐队小军鼓 25 Snare Roll小军鼓滚奏 26 Finger Snap 2响指2 (以上Roland SC-88 Pro)

27 Hi Q激光枪声 28 Synth Slap合成拍音 29 Scratch 2高音刷音 30 Scratch 1低音刷音 31 Sticks鼓槌 32 Square Click敲方板 33 Metronome Click节拍器 34 Metronome Bell节拍器重音 35 Acoustic Bass Drum低音大鼓 36 Bass Drum 1高音大鼓 37 Side Stick鼓边 38 Acoustic Snare小鼓 39 Hand Clap拍手声 40 Electric Snare电子小鼓 41 Low Floor Tom低音落地嗵鼓 42 Closed Hi-Hat合音踩镲 43 High Floor Tom高音落地嗵鼓 44 Pedal Hi-Hat踏音踩镲 45 Low Tom低音嗵鼓 46 Open Hi-Hat开音踩镲 47 Low-Mid Tom中低音嗵鼓 48 Hi-Mid Tom中高音嗵鼓 49 Crash Cymbal 1低砸音镲 50 High Tom高音嗵鼓 51 Ride Cymbal 1低浮音镲 52 Chinese Cymbal中国镲 53 Ride Bell浮音镲碗 54 Tambourine铃鼓 55 Splash Cymbal溅音镲 56 Cowbell牛铃 57 Crash Cymbal 2高砸音镲 58 Vibraslap颤音叉 59 Ride Cymbal 2高浮音镲 60 Hi Bongo高音邦戈 61 Low Bongo低音邦戈 62 Mute Hi Conga弱音康加 63 Open Hi Conga高音康加 64 Low Conga低音康加 65 High Timbale高音铜鼓 66 Low Timbale低音铜鼓 67 High Agogo高音拉丁打铃 68 Low Agogo低音拉丁打铃 69 Cabasa沙锤 70 Maracas响葫芦 71 Short Whistle短哨 72 Long Whistle长哨 73 Short Guiro短锯琴 74 Long Guiro长锯琴 75 Claves击杆 76 Hi Wood Block高音木块 77 Low Wood Block低音木块 78 Mute Cuica弱音吉加 79 Open Cuica开音吉加 80 Mute Triangle弱音三角铁 81 Open Triangle开音三角铁 82 Shaker沙锤(比69沙锤高) 83 Jingle Bell铃铛 84 Bell Tree铃树 85 Castanets响板 86 Mute Surdo弱音瑟多 87 Open Surdo开音瑟多 88 Applause2欢呼2(Roland SC-88Pro)

MIDI CC List(常用的标红)

0 Bank Select (MSB)

1 Modulation Wheel

2 Breath controller

3 = Undefined

4 Foot Pedal (MSB)

5 Portamento Time (MSB)

6 Data Entry (MSB)

7 Volume (MSB)

8 Balance (MSB)

9 = Undefined

10 Pan position (MSB)

11 Expression (MSB)

12 Effect Control 1 (MSB)

13 Effect Control 2 (MSB)

14 = Undefined

15 = Undefined

16-19 = General Purpose

20-31 = Undefined

32-63 = Controller 0-31

64 Hold Pedal (on/off)

65 Portamento (on/off)

66 Sostenuto Pedal (on/off)

68 Legato Pedal (on/off)

69 Hold 2 Pedal (on/off)

70 Sound Variation

71 Resonance (Timbre)

72 Sound Release Time

73 Sound Attack Time

74 Frequency Cutoff (Brightness)

75 Sound Control 6

76 Sound Control 7

77 Sound Control 8

78 Sound Control 9

79 Sound Control 10

80 Decay or General Purpose Button 1 (on/off) Roland Tone level 1

81 Hi Pass Filter Frequency or General Purpose Button 2 (on/off) Roland Tone level 2

82 General Purpose Button 3 (on/off) Roland Tone level 3

83 General Purpose Button 4 (on/off) Roland Tone level 4

84 Portamento Amount

85-90 = Undefined

91 Reverb Level

92 Tremolo Level

93 Chorus Level

94 Detune Level

95 Phaser Level

96 Data Button increment

97 Data Button decrement

98 Non-registered Parameter (LSB)

99 Non-registered Parameter (MSB)

100 Registered Parameter (LSB)

101 Registered Parameter (MSB)

102-119 = Undefined

120 All Sound Off

121 All Controllers Off

122 Local Keyboard (on/off)

123 All Notes Off

124 Omni Mode Off

125 Omni Mode On

126 Mono Operation

127 Poly Mode

音高编号对照表

Mido官方文档 

Mido官方文档

  • 30
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值