第一键盘 - 电子琴在线论坛

 找回密码
 现在注册

QQ登录

只需一步,快速开始

查看: 10713|回复: 16

读书笔记——MIDI文件结构简介

  [复制链接]

发表于 2012-3-4 15:22:40 | 显示全部楼层 |阅读模式
本帖最后由 华北kingsky 于 2012-3-4 15:28 编辑

       庆祝论坛乔迁新居!我也把我几个月来研究MIDI文件的一些心得体会,贴到坛子上分享。哈哈,每天上下班在路上的一个多小时里,我拎着一个上网本,在公交车上慢慢地敲字~时间宝贵,这一个多小时的时间也要充分利用~
       但这些东东毕竟是我个人的理解,难免有错误出现。也欢迎各位大虾批评指正,我会把一些修改的内容通过附录添加到文章末尾。
       阅读本文的话,读者需要有基础性的电脑和MIDI音乐相关知识:
· 基本的计算机相关知识,如十六进制数的表示法,十六进制与十进制、二进制之间的转换方法等;
· 基本的乐理知识,如音高、时长、节拍、曲调等;
· MIDI协议基础,如各种MIDI事件的类型和含义,事件状态字、数据长度等的知识;
· MIDI文件的编辑制作经验,如音序器(硬件或软件)基于音轨的操作思想和使用方式等。
参考资料的话,推荐读者去找一下《MIDI原理与开发应用》这本书,国防工业出版社出版,陈学煌、刘永志、潘晓利、马俊编著,红色封皮。同时读者还可以去网上搜索相关的资料,有几个帖子写得也不错。
下面是正文:
================

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组成。读者若使用一个十六进制编辑软件(如UltraEdit)打开并查看一个MID文件时,便能找到这两部分。

        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文件时,首先要找到各个块,也就是一个MThd chunk和若干MTrk chunk的ASCII字符串。这样用户或软件才能根据MThd和MTrk中所记录的信息,确定此MIDI文件的基本参数,并进行下一步更详细的解析。关于MThd和MTrk中详细的解析规则,笔者将在下文中具体解释。

        以常用的音序器软件cakewalk为例,在cakewalk软件打开一个MIDI文件后,软件将读取MThd chunk内部的相关信息,从而确定此文件的类型、chunk数目、全局时钟设置等参数。然后软件将读取各个MTrk chunk,并将每个chunk内部包含的MIDI事件列在编辑区内对应的音轨部位。于是,在使用cakewalk打开一个MIDI文件后,用户就可以在编辑区内部看到若干条黄色的音轨,每个音轨内部含有该音轨所包含的MIDI事件,用户可以对不同音轨内的不同事件,甚至整条音轨,进行参数设置。



1)       MThd 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,不记录实际的演奏信息)。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数目。这种度量法在单纯的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。



2)       MTrk 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事件
        ……                      // (省略)③Delta-time及MIDI事件
        00 FF 2F00         // ④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,随后的FF2F 00为一段meta-event,代表了本Track结束。



3)       Delta-Time及MIDI事件结构

        delta-time,实际上就是“Δt”。任何学习过数学和物理学的人,都能明白“Δt”的含义:它代表着时间差。

         MIDI系统中的delta-time,表征着下一个事件距离上一个事件有多长时间,即两个事件之间的时间差。这个时间不是我们日常生活中的时分秒,而是MIDI Tick。音序器通过对自身产生的MIDI Tick进行计数,判断是否该处理下一个MIDI事件。如果Tick数到达delta-time,就处理下一个事件,然后继续判断下一个delta-time是否到达,周而复始。

        为了能够表示足够长的时间,Delta-time使用可变长度数的格式,最长可以表示0x0fffffff。

        MIDI事件则包括实际需要发送出去的MIDI事件,和meta-event事件。对于实际需要发送的数据,音序器就直接将数据发送出去;如果是meta-event事件,音序器则修改自身的相关参数。

        这里要注意MIDI文件的“状态字省略”特点。为了减少MIDI文件的体积,人们规定:如果同一Track Chunk中的下一条MIDI事件,和上一条事件,属于同一类型同一通道的事件(即状态字相同)时,下一条事件的状态字可以省略,而只需记录数据。音序器碰到这种情况时,应自动填充上一事件的状态字。

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

    00 93 3C6B  // 音符0x3CNote-On,力度为0x6B
    70 3C00     // 实际发送的指令为隔0x70 Ticks之后,发送93 3C 00。

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

         总结起来,MIDI文件可以用以下的两张图来描述。通过这两张图,相信读者会对MIDI文件的数据结构建立一个直观的认识。本文的目的就在此。有关MIDI文件更细节的说明,请参考相关的MIDI文档和手册。相信在对MIDI文件有了一个基本认识之后,您再翻阅其他技术文档和手册时,就可以更加深刻地理解其含义了。

        图一为MIDI 0格式文件的大概结构;图二为MIDI 1格式文件的大概结构。
MIDI0结构.jpg


MIDI1结构.jpg

大图下载: MIDI结构大图.rar (543.01 KB, 下载次数: 174)
回复

使用道具 举报

发表于 2012-3-4 20:16:12 | 显示全部楼层
楼主辛苦了
回复 支持 反对

使用道具 举报

发表于 2012-3-6 18:02:33 | 显示全部楼层
很好,收藏有待以后研究一下~~
回复 支持 反对

使用道具 举报

发表于 2012-11-3 17:52:14 | 显示全部楼层
已阅读~~好贴!居然有MIDI2格式,第一次听说……不知道有成品不?不过就算有S550也没法播放,不支持……MIDI2格式是每个轨道速度可以不一样?好奇怪
要查看MIDI文件的格式只要看图上的第三行数字就可以了吧?在cakewalk怎么查看呢
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-9 21:21:44 | 显示全部楼层
为你弹琴 发表于 2012-11-3 17:52
已阅读~~好贴!居然有MIDI2格式,第一次听说……不知道有成品不?不过就算有S550也没法播放,不支持……MID ...

我也是没见过midi 2格式的文件,对midi 2文件我也只是根据书上的描述,然后自己主观认为的,不知道是否正确……
在cakewalk里好像查看不了,或者说我没找到……我是直接用一个十六进制编辑器来查看,如UltraEdit,既可以作为普通的文本编辑器,又能用来做十六进制编辑器,写程序代码也可以关键字高亮,很方便~这个软件用得很多的,下载也很方便~
回复 支持 反对

使用道具 举报

发表于 2012-11-9 21:57:16 | 显示全部楼层
华北kingsky 发表于 2012-11-9 21:21
我也是没见过midi 2格式的文件,对midi 2文件我也只是根据书上的描述,然后自己主观认为的,不知道是否正 ...

哦,好的
回复 支持 反对

使用道具 举报

发表于 2013-2-19 16:54:26 | 显示全部楼层
拜读了,谢谢楼主!眼界又开阔了。
回复 支持 反对

使用道具 举报

发表于 2013-3-4 18:03:53 | 显示全部楼层
楼主辛苦了!
回复 支持 反对

使用道具 举报

发表于 2013-4-13 19:26:29 | 显示全部楼层
此帖必火!
回复 支持 反对

使用道具 举报

发表于 2014-8-3 16:39:46 | 显示全部楼层
好像电钢都不支持 MIDI2 格式
回复 支持 反对

使用道具 举报

发表于 2014-8-15 09:44:40 | 显示全部楼层
谢谢!学习中。。。。。。
回复 支持 反对

使用道具 举报

发表于 2014-8-31 07:55:10 | 显示全部楼层
感谢楼主无私奉献资料。
回复 支持 反对

使用道具 举报

发表于 2015-8-23 11:38:21 | 显示全部楼层
学习了,O(∩_∩)O谢谢!
回复 支持 反对

使用道具 举报

发表于 2015-11-16 17:05:46 | 显示全部楼层
楼主 midi 1 的  多轨道 同步 是怎么实现 的 呢
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-11-16 17:18:56 | 显示全部楼层
suncpu 发表于 2015-11-16 17:05
楼主 midi 1 的  多轨道 同步 是怎么实现 的 呢

      我想的办法可能是:

      因为MIDI 1格式是所有Track共用一个全局的MIDI时钟,所以就设置一个全局的MIDI Tick时钟用来计时。每一个Track里的每个Event,都是有delta-time的,所以把自己Track里已播放的delta-time累加起来,就是当前的播放进度。就是说,所有Track都看这个全局时钟。

      具体怎么做,我提供不了,因为我自己也没完成过MIDI文件解码这一块的东西。所以只能提供思路……
回复 支持 反对

使用道具 举报

发表于 2016-9-21 23:04:34 使用第一键盘发送 | 显示全部楼层
神啊,俺是一头雾水
回复 支持 反对

使用道具 举报

发表于 2017-8-25 18:19:29 | 显示全部楼层
很好,收藏有待以后研究一下
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 现在注册

本版积分规则

关闭

新闻头条上一条 /1 下一条

【重要通知】|申请友链|Archiver|手机版|第一键盘 - 电子琴信息网 - 电子琴在线论坛 ( 粤ICP备14036084号 )

GMT+8, 2024-3-29 18:24 , Processed in 0.186960 second(s), 42 queries .

Powered by Discuz! X3

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表