midi文件解析 by python mido

首先要记住,midi文件并不存储声音,只存储指示合成器(电子乐器)如何发声的动作,比如某个时刻以什么力度按下某个音符。

音符note

狭义的音符指C、D、E、F、G、A、B七个(即Do–Re–Mi–Fa–Sol–La–Si)。广义的应该是包括音符与对应的音阶,比如中央C即C4。音符、琴键与它们在midi中编号的关系如下图所示:

在midi信息中的note number从[21, 108]对应着钢琴的88个琴键

midi channels

通道,最多有16个通道。可以了解为每个通道对应一个物理输出,所以midi最多可以同时控制16种乐器。

midi tracks

音轨,音轨与通道并不是一一对应,而是可以多对多的关系。音轨是逻辑上的划分,比如可以将钢琴的左手演奏放在track 1,右手演奏放在track 2。但是输出时候,都是对应输出到钢琴的通道。你也可以只设置一个track 1,并且在里面记录着不同通道的消息。另外,还经常将track 0用来存储midi的元信息。

event

事件,也叫做消息(在mido库中使用message表示)。包括三种事件meta event,midi event, sysex event。

meta messages

元消息是不会通过物理线路发送给设备的,只是存储在midi文件中。元消息通常用来存储作者、版权、音轨名、乐器名等信息,以及最重要的tempo值(即microseconds per beat)

midi messages

midi消息中最主要的就是note_on, note_off消息。note_on消息表示敲击某个琴键,同时携带参数表示音符的音高与敲击力度。note_off相应的就是释放该键。note_on与note_off消息必须是一一对应的(除非是打击乐乐器,可以不接收note_off消息)

一个note_on消息由如下三个字节组成:

    • Status byte : 1001 CCCC
    • Data byte 1 : 0PPP PPPP
    • Data byte 2 : 0VVV VVVV

其中CCCC表示midi通道(从0到15,表示通道1~通道16)。

PPP PPPP表示音符号码(从0到127),60即中央C。

VVV VVVV表示力度(从0到127,如果设置位0,其实就等同于note_off消息)。

note_off消息的三个字节同note_on类似,只是其状态码位1000 CCCC,而且力度通常设置为0。

sysex message

系统额外消息,是给不同的midi系统保留的存储自家系统信息的。一般不用管他。

Variable Length Quantity

可变长度的数值表示法(变长数)。每个字节有8位,只使用7位用来表示[0, 127]之间的数值,第一位用来做标志位,0表示这个字节是该变长数的最后一个字节,1表示后面跟着的一个字节也属于该变长数。

举一个可变长度数的例子:81 70,那么这个delta-time实际表示的数为0xF0。怎么来的?0x81的二进制为10000001,后边还有数据;0x70的二进制为01110000,后面没有数据了,这个变长数表示完毕。把下划线部分的数组合起来,就变成了:000 0001111 0000,就是1111 0000,0xF0。

再举个例子:E3 9A B4 70,这个可变长度数实际上表示的是0xC669A70。这串数是这么变化的:0xE3 = 11100011,后面还有数据;0x9A = 10011010,后面还有数据;0xB4 = 1011 0100,后面还有数据;0x70 = 01110000,后面没有数据了,这个变长数表示完毕。把下划线部分的数组合起来,就变成了:1100 0110 0110 1001 1010 0111 0000 = 0xC669A70.
作者:SkyKingATbj
https://www.bilibili.com/read/cv1763510/
出处: bilibili

delta-time

midi文件中每个事件的前面都有一个变长数表示的delta-time,用来标识该事件距离上一个事件经过了多少个ticks。

<delta_time>is the number of ‘ticks’ from the previous event, and is represented as a variable length quantity。

使用mido库解析midi文件后,每个message对象都有一个time属性,根据不同的使用方式,time属性有两种不同数据类型(int,float)

  • inside a track, it is delta time in ticks. This must be an integer.
  • in messages yielded from play(), it is delta time in seconds (time elapsed since the last yielded message)

 

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top