midi文件格式简析 您所在的位置:网站首页 mid格式是什么意思 midi文件格式简析

midi文件格式简析

2024-01-20 17:18| 来源: 网络整理| 查看: 265

最近用C++写了个midi文件转简谱之类的程序,想到这几天看了很多midi文件格式解析的文章,为了以后忘记midi文件格式的时候不用再去网上到处找文章顺便分享一下经验,所以写了这个博客用来记录一下

当然只看一篇文章不太可能彻底理解midi文件的格式,因为大家写的都或多或少有自己的侧重点,所以这里列出我学习midi格式的时候看的一些文章。

1、读书笔记——MIDI文件结构简介(https://www.bilibili.com/read/cv1753143)

2、音乐研发必备:理解 MIDI 协议与标准 MIDI 文件格式(https://mp.weixin.qq.com/s/m1mujGaEkwSkpMwYijjISw)

3、MIDI文件格式 百度百科(https://baike.baidu.com/item/MIDI%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F/8225161?fr=aladdin)

midi格式简析

如果我们用二进制文本阅读器打开midi文件,可以看到以下内容,这里我们以VScode的Hex Editor为例

midishow下载的TruE.mid

 我们先从midi文件的主体结构开始:

 MThd

 MTrk

 ...

 MTrk

MThd(header chrunk),也就是midi文件的头文件音轨,用于记录midi文件的类型、音轨数量、时间格式。

MTrk(Track Chunk),是midi文件真正存储音轨的部分,后文我们会对它进行具体分析。

MThd存储格式

midi文件的开头一定是MThd这四个字母的ASCII码,它用来让计算机辨别这个文件是否是midi文件。

随后的四个字节,一定是00 00 00 06,这四个字节存储的内容是MThd数据部分包含多少字节的数据(不包含MThd 00 00 00 06这八个字节),因为MThd的格式是固定的,它一定只包含6个字节,所以这部分一定是00 00 00 06。

之后两个字节,用于指定midi的格式:

00 00 单音轨

00 01 同步多音轨

00 02 不同步多音轨(几乎不会见到)

之后两个字节,用于指定midi文件的轨道数(也就是MTrk的数量)

一般来说,midi文件的第一个MTrk音轨叫做Tempo Map,只用于存储midi文件包含的信息而不包含音符信息,比如作曲者、歌曲名称、版权等等。

当然这个不是硬性的格式要求,有的midi文件也会不包含Tempo Map,直接存储有效音轨。

之后两个字节,第一个字节的高四位指定时间格式类型,0是tick计数,1是SMTPE计数

tick计数:后一个半字节记录了一个四分音符经过的tick数

SMPTE计数:后一个半字节记录了每秒的SMPTE帧的数量以及每个帧的tick(这个暂时不明确到底什么意思,还好这种格式用的不多,读者可以直接忽略掉)

MTrk存储格式

和MThd类似,开头是MTrk这四个字母的ASCII码。

随后的四个字节存储的内容是MTrk数据部分包含多少字节的数据(不包含MTrk和存储MTrk数据长度的这八个字节)

接下来就是MTrk的数据部分,我们把MTrk存储的数据单元称为事件

事件:由delta-time 和 midi事件两个部分构成

delta-time,顾名思义就是Δt,记录了这个事件和上个事件之间相差的时间,如果时间格式是ticks计数的话它的单位就是ticks。

为了能够存储尽可能大的时间差,delta-time采用了一种叫做动态字节的数据存储方式

动态字节:只使用字节的低七位用来存储数据。如果我要存储127这个数字,我只会使用一个字节的低七位,也就是01111111(7F)

如果我要存储128,我需要使用八个字节。这个时候,我们会使用字节的最高位,把他标记成1,代表这个字节和下一个字节合并起来存储数据,因此128将会使用两个字节,存储成 10000001 00000000 (81 00)

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

delta-time结束后紧随的就是midi事件了。

midi事件,由事件标记和事件数据两个部分组成。存储事件标记的字节的最高位一定是1,存储事件数据的字节的最高位一定是0。这一点在后文谈到的事件缩写中会起到作用。

事件标记:一个字节,用于说明事件的类型,具体情况如下表,x代表低四位可以是任何数

表中的最后一行是00~7F,也就是字节最高位为0。这种情况就是事件缩写,也就是这条事件的事件标记和上一条事件完全一样,这个事件不存储事件标记只存储事件数据。这种方法可以减少midi文件的占用内存,但是一般的制谱软件不会使用这种缩进,只有少部分软件生成的midi文件可能会使用到这种缩写方法。

这里具体分析以下8x和9x这两个和音符有关的事件,如果只是想要粗略的演奏midi文件我们只需要这两个事件数据就可以了。

这两个事件的第一个字节是音高,第二个字节是力度。

音高的数字大小为0~127,其中数字大小60对应着钢琴里的中央音符C3。

在此基础上音高每+1,就代表对应的钢琴键右移一位,每+12代表升高一个八度。

接着是事件标记为FF时的情况。FF一般用于存储作曲者、歌曲名称、版权、歌词之类的文本信息以及速度、节拍、调号之类的乐曲演奏信息。这里不作专门说明,我们只分析它的数据格式。

事件标记FF之后的第一个字节,用于说明FF对应的更具体的事件是什么。

之后一个字节,记录存储了多少字节长度的事件数据。

比如我要存储歌曲标题TruE,那么我的整个midi事件将会存储成这样子

00(第一个字节,代表delta-time=0)

FF(事件标记FF)

03(代表我要存储的内容是歌曲标题,具体可以查看百度百科对应的事件表)

04(代表我要存储长度为4个字节的标题文本)

TruE(这四个字母用ASCII码存储即可)

最后,是MTrk的结束记号。虽然MTrk开头已经存储了它的字节长度,但是它还是有结束标记。(我要不知道为什么,可能是为了避免数据传输过程中的损坏)

它也是以midi事件的方式存储在MTrk中的,对应着的midi事件是FF 2F 00,当读到这个事件的时候,就可以结束这个MTrk音轨的读取了。

总结

本文的目的在于让大家对MIDI文件有一个基本认识,这样子在读其他更深入分析的文章时不至于一头雾水。

最后是一些小吐槽:

midi歌曲的下载我一般会使用MidiShow这个网站,这个网站有个AI mp3转midi的小工具。它转出的midi文件就会使用midi事件缩写,也是我唯一见到的使用缩写的midi文件。(不知道为什么OvO)

浪费了两天折腾这东西本来是想做个原神自动输入midi文件就能弹琴的程序的,结果最后发现大部分midi文件的曲子都有升降调而且跨了4个大调,根本不能弹琴,可恶!(什么时候原琴加个按shift变成升降调的功能啊)



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有