Skip to content

标准 MIDI 文件规范 1.0

本文转自 STANDARD MIDI FILES SPECIFICATION – MIDI.org,如有侵权,请联系删除。

发布者:
MIDI 制造商协会
洛杉矶,加利福尼亚

RP001

1996 年 2 月修订版

版权所有 © 1988, 1994, 1996 MIDI 制造商协会

保留所有权利。未经 MIDI 制造商协会书面许可,不得以任何形式或任何手段复制本文件的任何部分,包括信息存储和检索系统。

MMA
POB 3173
La Habra CA 90632-3173

引言

本文档概述了 MIDI 文件的规范。MIDI 文件的目的是为不同计算机上的程序之间提供带时间戳的 MIDI 数据交换方式。该设计的主要目标之一是实现紧凑的表示方式,使其非常适合基于磁盘的文件格式,但可能不适合用于需要快速访问的程序内存存储(可以在读取或写入文件时轻松转换为快速访问格式)。此格式不是为了取代任何程序的常规文件格式,尽管在需要时可以用于该目的。

MIDI 文件包含一个或多个 MIDI 流,并为每个事件提供时间信息。它支持歌曲、序列和轨道结构,以及速度和节拍信息。轨道名称和其他描述信息可以与 MIDI 数据一起存储。该格式支持多轨和多序列,使得支持多轨的程序在转换文件时能够使用该格式。

该规范定义了文件中使用的 8 位二进制数据流。数据可以存储在二进制文件中,经过特殊编码以提高 MIDI 传输效率,转换为十六进制 ASCII,或以符号化形式翻译为可打印的文本文件。本规范主要讨论 8 位数据流的内容,而不涉及 MIDI 文件在 MIDI 上的传输方法。

序列、轨道、块:文件结构

约定

在本文档中,位 0 表示字节的最低有效位,位 7 表示最高有效位。

在 MIDI 文件中,一些数字以称为“可变长度数量”的形式表示。这些数字以每字节 7 位的方式表示,最高有效位在前。除最后一个字节外,所有字节的第 7 位都设置为 1,最后一个字节的第 7 位设置为 0。如果数字在 0 到 127 之间,则以一个字节表示。

以下是以可变长度数量表示的数字示例:

数字(十六进制)表示形式(十六进制)
0000000000
0000004040
0000007F7F
0000008081 00
00002000C0 00
00003FFFFF 7F
0000400081 80 00
00100000C0 80 00
001FFFFFFF FF 7F
0020000081 80 80 00
08000000C0 80 80 00
0FFFFFFFFF FF FF 7F

允许的最大数字是 0x0FFFFFFF,因此在编写可变长度数字的例程中,该表示必须适合 32 位。理论上可以表示更大的数字,但在每分钟 500 拍的快速节奏下,以 96 分音符的两倍持续四天,足够长用于表示任何延迟时间!

文件

对于任何文件系统而言,MIDI 文件就是一系列 8 位字节。在 Macintosh 上,该字节流存储在文件的数据部分(类型为'Midi'),或在剪贴板上(数据类型为'Midi')。大多数其他计算机将 8 位字节流存储在文件中,命名或存储约定会根据需要定义。

MIDI 文件由块组成。每个块有一个 4 字符的类型和一个 32 位长度,表示块中的字节数。此结构允许设计新的块类型,当程序遇到在其开发之前引入的块类型时,可以轻松忽略。你的程序应当能够识别不熟悉的块并将其视为不存在。

每个块以 4 个 ASCII 字符的类型开始。然后是一个 32 位长度,最高有效字节在前(例如,长度为 6 表示为 00 00 00 06)。该长度指的是后续数据字节的数量,不包括类型和长度本身。因此,一个长度为 6 的块在磁盘文件中实际上占用 14 个字节。这种块结构类似于 Electronic Arts 公司的 IFF 格式,此处描述的块可以很容易地放入 IFF 文件中。但 MIDI 文件本身不是 IFF 文件:它不包含嵌套块,块的长度也不要求是偶数。将其转换为 IFF 文件只需填充奇数长度块,并将整个内容放入一个 FORM 块中。

MIDI 文件包含两种类型的块:头块和轨道块。头块提供有关整个 MIDI 文件的最小信息。轨道块包含最多 16 个 MIDI 通道的数据流。通过多个轨道块,可实现多轨、多 MIDI 输出、模式、序列和歌曲等概念。

MIDI 文件总是以头块开始,后跟一个或多个轨道块。

text
MThd <头数据长度>
<头数据>
MTrk <轨道数据长度>
<轨道数据>
MTrk <轨道数据长度>
<轨道数据>
 ...

块描述

头块

文件开头的头块指定了文件数据的基本信息。完整块的语法如下:

text
<头块> = <块类型> <长度> <格式> <轨道数> <时间单位>

如上所述,<块类型>是 4 个 ASCII 字符'MThd';<长度>是一个 32 位表示的数字 6(高位字节在前)。

数据部分包含三个 16 位字,以最高有效字节在前的顺序存储。

第一个字<格式>指定了文件的总体组织形式。只有三个<格式>值被定义:

  • 0:文件包含一个多通道轨道。
  • 1:文件包含一个或多个同时进行的轨道(或 MIDI 输出)。
  • 2:文件包含一个或多个独立的单轨模式。

第二个字 <轨道数> 是文件中轨道块的数量。对于格式 0 的文件,它始终为 1。

第三个字 <时间单位> 指定了增量时间的含义。它有两种格式,一种用于度量时间,另一种用于基于时间码的时间:

  • 如果 <时间单位> 的第 15 位是 0,则第 14 到 0 位表示构成四分音符的增量时间“刻度”数量。例如,如果 <时间单位> 为 96,则文件中两个事件之间的时间间隔为八分音符时,增量时间为 48。

  • 如果 <时间单位> 的第 15 位是 1,文件中的增量时间对应于秒的细分,符合 SMPTE 和 MIDI 时间码的方式。第 14 到 8 位包含-24、-25、-29 或-30 之一,分别对应于四种标准的 SMPTE 和 MIDI 时间码格式(-29 对应于 30 刻度帧),表示每秒帧数。这些负数以二进制补码形式存储。第二个字节(正数)表示帧内的分辨率:典型值可能是 4(MIDI 时间码分辨率)、8、10、80(位分辨率)或 100。该系统允许精确指定基于时间码的轨道,但也可以通过指定 25 帧/秒和每帧 40 个单位的分辨率来实现基于毫秒的轨道。如果文件中的事件存储为 30 帧时间码的位分辨率,则 <时间单位> 字应为 E250 十六进制。

格式 0、1 和 2

格式 0 文件包含一个头块,后跟一个轨道块。这是数据最具互换性的表示形式。对于需要简单单轨播放器的程序来说,它非常有用,这类程序主要用于让合成器发出声音,但同时关注其他功能,如混音器或音效设备。即使你的程序基于轨道,能够生成这种格式也很有价值,以便与这些简单程序一起使用。此外,或许有人会编写一种将格式 1 转换为格式 0 的工具,这可能在某些情况下非常有用,以节省在程序中实现此功能的时间。

格式 1 或 2 文件包含一个头块,后跟一个或多个轨道块。支持多个同时轨道的程序应能够保存和读取格式 1 的数据,这是一种纵向一维形式,即轨道的集合。支持多个独立模式的程序应能够保存和读取格式 2 的数据,这是一种横向一维形式。提供这些最小功能将确保最大化的互换性。

在使用带有计算机和 SMPTE 同步器的 MIDI 系统中,使用歌曲指针和时钟同步,节奏图(描述整个轨道的速度,并可能包括节拍信息,以便推导小节号)通常由计算机创建。为了在同步器中使用它们,必须将这些数据从计算机转移出去。为了便于同步器从 MIDI 文件中提取这些数据,速度信息应始终存储在第一个 MTrk 块中。对于格式 0 文件,速度将在轨道中分布,速度图读取器应忽略其中的中间事件;对于格式 1 文件,速度图必须存储在第一个轨道中。

对于格式 0 文件,至少应在单个多通道轨道的开头包含速度和节拍的元事件。对于格式 1 文件,这些元事件应包含在第一个轨道中。对于格式 2 文件,每个时间上独立的模式都应至少包含初始的速度和节拍信息。

我们可能会定义其他格式 ID,以支持其他结构。遇到未知格式 ID 的程序,仍可以读取文件中的其他 MTrk 块(如果用户可以理解它们并将其组织为某种结构)。此外,未来可能会向 MThd 块中添加更多参数:即使长度超过 6,程序也应读取并尊重该长度。

轨道块

轨道块(类型为 MTrk)是实际存储歌曲数据的地方。每个轨道块都是 MIDI 事件(以及非 MIDI 事件)的连续流,前面有增量时间值。轨道块的格式(如下所述)对于所有三种 MIDI 文件格式(0、1 和 2)完全相同。

MTrk 块的语法如下(+ 表示“一个或多个”:至少应存在一个 MTrk 事件):

text
<轨道块> = <块类型> <长度> <MTrk事件>+

MTrk 事件的语法非常简单:

text
<MTrk事件> = <增量时间> <事件>

<增量时间> 以可变长度数量存储,表示在接下来的事件发生之前的时间量。如果轨道中的第一个事件发生在轨道的最开头,或两个事件同时发生,则使用增量时间 0。增量时间始终存在(不存储增量时间 0 时,任何其他值至少需要两个字节,而大多数增量时间不为零)。增量时间的单位为头块中指定的“刻度”。

text
<事件> = <MIDI事件> | <sysex事件> | <meta事件>

MIDI 事件 是任何 MIDI 通道消息。使用运行状态:如果前一个事件是相同状态的 MIDI 通道消息,可以省略 MIDI 通道消息的状态字节。每个 MTrk 块中的第一个事件必须指定状态。增量时间不是事件本身,而是 MTrk 事件语法的组成部分。请注意,运行状态在增量时间之间也适用。

sysex 事件 用于指定 MIDI 系统独占消息,既可以作为一个整体,也可以以数据包形式出现,或者作为“转义”来传输原本不合法的字节,例如系统实时消息、歌曲指针或选择、MIDI 时间码等。标准的完整系统独占消息在 MIDI 文件中以如下方式存储:

text
F0 <长度> <F0之后要传输的字节>

其中,长度以可变长度数量存储,表示后续字节的数量,不包括 F0 和长度本身。例如,要传输的消息 F0 43 12 00 07 F7 在 MIDI 文件中存储为 F0 05 43 12 00 07 F7。需要包括 F7,以便 MIDI 文件的读取器知道已读取完整的消息。

还有一种不暗示 F0 应传输的 sysex 事件形式。这种形式可以用作“转义”,以传输原本不合法的内容,如系统实时消息或其他特殊数据。此事件使用 F7 代码:

text
F7 <长度> <要传输的所有字节>

一些合成器制造商规定其系统独占消息需要分成小包传输。每个包只是整个系统独占消息的一部分,但其传输时间很重要。例如 CZ 补丁传输或 FB-01 的“系统独占模式”中使用的微音阶数据。F0 和 F7 sysex 事件可以组合使用,将语法完整的系统独占消息拆分为按时间传输的数据包。

F0 sysex 事件用于系列中的第一个包,传输时包含 F0。F7 sysex 事件用于后续包,不以 F0 开头(F7 不算作系统独占消息的一部分)。

一个语法完整的系统独占消息必须以 F7 结束,即使实际设备没有发送一个,以便在不查看 MIDI 文件中下一个事件的情况下知道已到达 sysex 消息的结尾。如果它存储为一个完整的 F0 sysex 事件,最后一个字节必须是 F7。如果它被分成多个包,最后一个包的最后一个字节必须是 F7。多包系统独占消息之间不得有可传输的 MIDI 事件。

多包系统独占消息的 MIDI 文件示例:假设要传输的字节为 F0 43 12 00,随后延迟 200 个刻度,再传输字节 43 12 00 43 12 00,再延迟 100 个刻度,最后传输字节 43 12 00 F7,这在 MIDI 文件中表示为:

text
F0 03 43 12 00
81 48    // 200个刻度的增量时间
F7 06 43 12 00 43 12 00
64       // 100个刻度的增量时间
F7 04 43 12 00 F7

在读取 MIDI 文件时,如果遇到没有前面的 F0 系统独占事件来开始多包系统独占消息序列的 F7 系统独占事件,应假设该 F7 事件作为“转义”使用。在这种情况下,不需要以 F7 结尾,除非希望 F7 被传输。

元事件 指定了对该格式或音序器有用的非 MIDI 信息,语法如下:

text
FF <类型> <长度> <数据>

所有元事件都以 FF 开头,接着是一个事件类型字节(始终小于 128),然后是以可变长度数量表示的数据长度,最后是数据本身。如果没有数据,则长度为 0。与块一样,未来可能会设计出尚未为现有程序所知的新元事件,因此程序必须正确忽略它们,并且预期会遇到它们。程序不得忽略未识别元事件的长度,并且不应因其大于预期而感到意外。如果发生这种情况,程序必须忽略它们已知内容之后的所有数据。然而,程序不得在元事件末尾添加任何自己的内容。

系统独占事件和元事件会取消当前生效的运行状态。运行状态不适用于这些消息,且不得用于它们。

meta 事件

以下是定义的几个 meta 事件,程序无需支持所有这些事件。

在每个 meta 事件的语法描述中,使用了一套约定来描述事件的参数。每个事件开始的 FF,事件的类型,以及不含可变数据量的事件长度,均以十六进制直接表示。像 ddse 这样的符号,由两个小写字母组成,表示 8 位的值。四个相同的小写字母(如 wwww)表示 16 位值,以最高有效字节优先的顺序存储。六个相同的小写字母(如 tttttt)表示 24 位值,也以最高有效字节优先的顺序存储。符号 len 表示 meta 事件语法中的长度部分,即一个数字,按可变长度数量存储,用以指定 meta 事件中其后的数据字节数。符号 textdata 表示由长度部分指定的任意数量的(可能为文本)数据字节。

一般来说,同一时间发生在轨道中的 meta 事件可以按任意顺序出现。如果使用版权事件,应尽早放置在文件中,以便于被注意到。序列号和序列/轨道名称事件(如果存在)必须出现在时间 0 处。轨道结束事件必须作为该轨道中的最后一个事件出现。

初步定义的 meta 事件包括:

  • FF 00 02 ssss 序列号

    可选事件,必须在轨道开头出现,在任何非零增量时间和任何可传输的 MIDI 事件之前。用于指定序列的编号。在格式 2 的 MIDI 文件中,用于标识每个“模式”,以便“歌曲”序列使用 Cue 消息引用这些模式。如果 ID 编号省略,则使用文件中序列的顺序位置作为默认值。在格式 0 或 1 的 MIDI 文件中,此编号应包含在第一个(或唯一)轨道中。如果需要传输多个多轨序列,则必须作为格式 1 文件组来实现,每个文件具有不同的序列号。

  • FF 01 len text 文本事件

    任何描述内容的文本。通常建议在轨道的开头放置一个文本事件,内容包括轨道的名称、预定的编排描述以及用户希望放入的任何其他信息。文本事件也可以在轨道的其他位置出现,用作歌词或描述提示点的文本。该事件中的文本应使用可打印的 ASCII 字符,以确保最大程度的互通性。然而,也可以使用带有高位的其他字符代码,用于不同程序之间在同一计算机上交换文件,这些程序支持扩展字符集。对于不支持非 ASCII 字符的计算机上的程序,应忽略这些字符。

    Meta 事件类型 01 至 0F 保留用于各种类型的文本事件,每种文本事件都符合上述文本事件的规范,但用于不同的目的。

  • FF 02 len text 版权声明

    包含版权声明的可打印 ASCII 文本。声明应包括字符“(C)”、版权年份和版权持有人。如果一个 MIDI 文件中有多个音乐片段,所有的版权声明应集中在此事件中,以便在文件开头显示。此事件应为第一个轨道块中的第一个事件,时间为 0。

  • FF 03 len text 序列/轨道名称

    如果是格式 0 的轨道,或者格式 1 文件中的第一个轨道,则为序列的名称。否则,为轨道的名称。

  • FF 04 len text 乐器名称

    描述该轨道中将使用的乐器类型。可以与 MIDI 前缀 Meta 事件一起使用,指定此描述适用的 MIDI 通道,或者通道也可以作为文本直接写入事件中。

  • FF 05 len text 歌词

    要演唱的歌词。通常,每个音节将是一个独立的歌词事件,并在该事件的时间点开始。

  • FF 06 len text 标记

    通常在格式 0 轨道中,或者格式 1 文件中的第一个轨道。序列中该点的名称,例如排练字母或节名称(“第一段”,“副歌”,等)。

  • FF 07 len text 提示点

    描述电影、视频屏幕或舞台上在该音乐得分点发生的事件(例如,“车撞进房子”,“帷幕拉开”,“她扇了他的脸”,等)。

  • FF 20 01 cc MIDI 通道前缀

    此事件中的 MIDI 通道(0-15)可用于将一个 MIDI 通道与随后的所有事件(包括系统独占和 meta 事件)关联。此通道“有效”直到遇到下一个正常的 MIDI 事件(包含通道信息)或下一个 MIDI 通道前缀 meta 事件。如果 MIDI 通道对应于“轨道”,此消息可帮助将多个轨道结合成一个格式 0 文件,同时保持它们的非 MIDI 数据与轨道关联。这种功能在 Yamaha 的 ESEQ 文件格式中也有类似实现。

  • FF 2F 00 轨道结束

    此事件是必需的。它指定轨道的确切结束点,以便精确指定轨道的长度,这对于需要循环或连接的轨道是必要的。

  • FF 51 03 tttttt 设置节奏(单位:微秒每 MIDI 四分音符)

    此事件指示节奏变化。另一种表达“微秒每四分音符”的方式是“每 MIDI 时钟的微秒数”。将节奏表示为每拍所需的时间,而不是每时间单位多少拍,能够实现与基于时间的同步协议(如 SMPTE 时间码或 MIDI 时间码)的绝对精确长期同步。这个节奏分辨率提供的精度使得一首 4 分钟的曲目以 120 拍每分钟的速度,能够在曲目结束时保持 500 微秒的准确度。理想情况下,这些事件应仅在 MIDI 时钟位置处发生——此约定旨在确保或至少提高与其他同步设备的兼容性,以便将此格式中存储的时间签名/节奏图轻松传输到其他设备。

  • FF 54 05 hr mn se fr ff SMPTE 偏移

    如果存在此事件,它指定轨道块应开始的 SMPTE 时间。应出现在轨道的开头,即在任何非零增量时间之前,并且在任何可传输的 MIDI 事件之前。小时数必须以 SMPTE 格式编码,就像在 MIDI 时间码中一样。在格式 1 文件中,SMPTE 偏移必须与节奏图一起存储,在其他轨道中没有意义。ff 字段包含分数帧,以每帧 100 分之一为单位,即使在指定了不同帧细分的 SMPTE 轨道中,增量时间仍按此单位表示。

  • FF 58 04 nn dd cc bb 拍号

    拍号表示为四个数字。nn 和 dd 表示拍号的分子和分母,按照标记方式表示。分母是负的 2 的幂:2 代表四分音符,3 代表八分音符,依此类推。cc 参数表示每个节拍中的MIDI 时钟数。bb 参数表示 MIDI 认为的每个四分音符(24 个 MIDI 时钟)中有多少个标记的 32 分音符。之所以添加这一点,是因为已经有多个程序允许用户指定 MIDI 认为的四分音符(24 个时钟)应以或与其他东西相关的方式进行标记。

    因此,表示 6/8 拍号的完整事件,其中节拍器每三拍八分音符点击一次,但每四分音符有 24 个时钟,每小节 72 个时钟,应该是(十六进制):

    text
    FF 58 04 06 03 24 08

    即 6/8 拍(8 是 2 的 3 次方,因此是 06 03),每个附点四分音符 36 个 MIDI 时钟(24 十六进制!),每个 MIDI 四分音符八个标记的 32 分音符。

  • FF 59 02 sf mi 调号

    sf = -7: 7 个降号
    sf = -1: 1 个降号
    sf = 0: C 大调
    sf = 1: 1 个升号
    sf = 7: 7 个升号
    mi = 0: 大调
    mi = 1: 小调

  • FF 7F len data 音序器特定的元事件

    特定音序器的要求可以使用此事件类型:数据的第一个字节或字节序列为制造商 ID(通常为一个字节,或者如果第一个字节是 00,则为三个字节)。如同 MIDI 系统独占一样,定义该事件的制造商应发布其使用方法,以便他人理解如何使用它。这个事件类型可能被音序器用作唯一的文件格式,而具有特定功能的音序器则可能仍然使用标准特性。

程序片段和示例 MIDI 文件

以下是一些用于读取和写入 MIDI 文件中可变长度数字的示例代码。代码使用 C 语言,使用 getcputc 函数从文件 infileoutfile 中读取和写入单个 8 位字符。

c
WriteVarLen (value)
register long value;
{
  register long buffer;
  buffer = value & 0x7f;
  while ((value >>= 7) > 0)
  {
    buffer <<= 8;
    buffer |= 0x80;
    buffer += (value & 0x7f);
  }
  while (TRUE)
  {
    putc(buffer,outfile);
    if (buffer & 0x80)
      buffer >>= 8;
    else
      break;
  }
}

doubleword ReadVarLen ()
{
  register doubleword value;
  register byte c;

  if ((value = getc(infile)) & 0x80)
  {
    value &= 0x7f;
    do
    {
      value = (value << 7) + ((c = getc(infile)) & 0x7f);
    } while (c & 0x80);
  }
  return (value);
}

以下是该摘录的 MIDI 文件示例。首先展示的是格式 0 的文件,其中所有信息混合在一起;然后展示的是格式 1 的文件,其中所有数据分为四个轨道:一个用于节奏和拍号,三个用于音符。使用每四分音符 96 个“刻度”的分辨率。虽然 4/4 拍号和 120 的速度被隐含,但在此明确陈述。

test.png

这个例子所表示的 MIDI 流的内容在这里进行了分解:

增量时间 (十进制)事件代码 (十六进制)其他字节 (十进制)备注
0FF 5804 04 02 24 084 个字节:4/4 拍,24 个 MIDI 时钟/点击,8 个 32 分音符/24 个 MIDI 时钟
0FF 5103 5000003 个字节:500,000 微秒每四分音符
0C05通道 1,程序变更 5
0C146通道 2,程序变更 46
0C270通道 3,程序变更 70
09248 96通道 3,音符开启#48,强奏
09260 96通道 3,音符开启#60,强奏
969167 64通道 2,音符开启#67,中强奏
969076 32通道 1,音符开启#76,钢琴
1928248 64通道 3,音符关闭#48,标准
08260 64通道 3,音符关闭#60,标准
08167 64通道 2,音符关闭#67,标准
08076 64通道 1,音符关闭#76,标准
0FF 2F00轨道结束

格式 0 的完整 MIDI 文件内容以十六进制表示如下:

首先是头块:

text
4D 54 68 64   MThd
00 00 00 06   块长度
00 00         格式 0
00 01         一个轨道
00 60         每四分音符96个刻度

然后是轨道块。它的头部,接着是事件(注意某些地方使用了运行状态):

text
4D 54 72 6B   MTrk
00 00 00 3B   块长度 (59)
增量时间事件备注
00FF 58 04 04 02 18 08拍号
00FF 51 03 07 A1 20节奏
00C0 05
00C1 2E
00C2 46
0092 30 60
003C 60运行状态
6091 43 40
6090 4C 20
81 4082 30 40双字节增量时间
003C 40运行状态
0081 43 40
0080 4C 40
00FF 2F 00轨道结束

格式 1 的文件表示稍有不同。

首先是头块:

text
4D 54 68 64   MThd
00 00 00 06   块长度
00 01         格式 1
00 04         四个轨道
00 60 96      每四分音符96个刻度

然后是节奏和拍号轨道的轨道块。它的头部,接着是事件:

text
4D 54 72 6B   MTrk
00 00 00 14   块长度 (20)
增量时间事件备注
00FF 58 04 04 02 18 08拍号
00FF 51 03 07 A1 20节奏
83 00FF 2F 00轨道结束

接下来是第一个音乐轨道的轨道块。此示例中使用了 MIDI 的音符开启/关闭运行状态约定:

text
4D 54 72 6B MTrk
00 00 00 10 块长度 (16)
增量时间事件备注
00C0 05
81 4090 4C 20
81 404C 00运行状态:音符开启,力度 = 0
00FF 2F 00轨道结束

然后是第二个音乐轨道的轨道块:

text
4D 54 72 6B   MTrk
00 00 00 0F   块长度 (15)
增量时间事件备注
00C1 2E
6091 43 40
82 2043 00运行状态
00FF 2F 00轨道结束

接着是第三个音乐轨道的轨道块:

text
4D 54 72 6B   MTrk
00 00 00 15   块长度 (21)
增量时间事件备注
00C2 46
0092 30 60
003C 60运行状态
83 0030 00双字节增量时间,运行状态
003C 00运行状态
00FF 2F 00轨道结束

计算增量时间

下面是一个如何将累计的增量时间转换为毫秒的例子。在最简单的情况下,所需的两条信息是:

  1. SMF 头块定义了“division”(每四分音符的增量时钟数)。例如,96 = 每四分音符 96 个时钟(ppq)(参考:SMF 1.0,第 4 页)。
  2. 节奏设置,它是一个非 MIDI 数据 Meta 事件,通常出现在 SMF 的第一个轨道中的时间增量时间 0 处。如果未指定节奏,默认节奏为 120 bpm。节奏以每四分音符的微秒数表示。例如,500000 = 120 bpm(参考:SMF 1.0,第 5、9 页)。

要将增量时间的时钟转换为毫秒,您只需做一个简单的代数计算:

时间(毫秒) = (时钟数) * (节奏(微秒/四分音符) / 分辨率(时钟/四分音符)) / 1000

例如,如果 Set Tempo 值为 500000 微秒每四分音符,分辨率为每四分音符 96 个时钟,则在 SMF 中第 6144 个时钟的时间为:

时间 = 6144 * (500000 / 96) / 1000 = 32000 毫秒

上述例子是一个非常简单的情况。实际上,SMF 文件中可以包含多个 Set Tempo Meta 事件,这些事件分布在文件的各个位置,因此为了计算任何时钟点的正确经过时间,必须执行一个持续的计算。

请注意,尽管时间签名不需要进行上述计算,但如果想要计算特定小节/拍的经过时间,则需要使用时间签名。与 Set Tempo 更改类似,时间签名也可以在 SMF 中随时发生变化,通常需要进行持续的计算以确定任何小节/拍的正确经过时间。

CC-BY-NC-4.0 Licensed