CWYAlpha

Just another WordPress.com site

Thought this was cool: MIDI 文件格式基础备忘

leave a comment »


基本概念与构成

    MIDI 是一种音频格式, 它用来描述声音在什么时间以什么音色多高的音调被发出或终止.
    一个 MIDI 文件中通常包含多个音轨 (track), MIDI 文件可以配置多个音轨以并行方式播放还是串行方式播放, 一般应用上多个音轨当然是同时播放比较好.
    为什么要有多个音轨呢? 这个… 写代码的时候一般也不会把所有代码塞在一个函数里面吧.
    而一个音轨就是一系列事件构成的. 事件包括了演奏或停止直接与发声有关的, 以及换乐器这样的控制指令.

文件内容组成

    MIDI 文件全景可以用下面的产生式来描述.

MIDI =>
    文件头部信息 音轨集

# 至少得有一个音轨
音轨集 =>
    音轨 音轨集
    |
    音轨

    下面解释其中的细节.

文件头部信息详情

文件头部信息 =>
    "MThd" 0x00000006 音轨类型 音轨数 节拍描述

    任何 MIDI 文件开头都由 “MThd” 四个字母 ASCII 码构成.
    接下来 0x00000006 是一个四字节类似大端码表示的整数 (是 0x00 0x00 0x00 0x06 而不是 0x06 0x00 0x00 0x00), 它的含义是文件头部剩下多少个字节. 本来这东西设计是可变的, 不过实际上后面 “音轨类型” “音轨数” “节拍描述” 每个都是固定的 2 字节, 因此这里直接填 6 就行.

    音轨类型有三种取值

  • 0 : 只有一个音轨
  • 1 : 多个音轨, 同时播放
  • 2 : 多个音轨, 串行播放

一般来说这个就取 1 (同样编码为 0x00 0x01, 而不是 0x01 0x00, 后面的音轨数, 节拍描述同样).

    音轨数也是一个 2 字节整数, 表示后面实际含有的音轨数量.
    节拍描述是一个 2 字节整数, 表示一个四分音符 tick 数. (本人没学过乐理, 这信息是照搬过来的)

音轨结构详情

音轨 =>
    "MTrk" 音轨内容长度 事件集 0x00ff2f00

    类似整个文件的开头, 音轨开头固定由 “MTrk” 四个字符填充. 此外, 音轨以固定的四字节 0x00 0xff 0x2f 0x00 结尾.
    音轨内容长度是 4 字节整数, 大端表示. 这个整数指示音轨中事件集的字节数加上末尾的 0x00ff2f00 填充物这 4 个字节.

事件集与事件结构

事件集 =>
    事件 事件集
    |
    ε

事件 => 相对时间 事件类型 事件参数

    相对时间是一个整数, 单位是 tick, 指的是该事件相对于上一个事件延迟多久. 如第一个事件发生在 0x08, 第二个事件的相对时间如果是 0x18, 则实际发生时间会是 0x20.
    比较麻烦的是, 这个时间并不是固定字节长度的, 它的构成大概如下:

  • 如果这个数在 0~127 之间, 则用 1 字节表示, 否则
  • 最后一个字节存放这个数对 128 的模, 再将这个数地板除 128 得到的商以递归的方式在前面 (较低位置) 表示
  • 除了最后一个字节, 其它字节最高为均为 1 (这样判别何处结束)

这样还是太抽象, 举个例子, 100 这个数, 小于 127, 因此可以直接在一个字节内表示为 0x64; 如果是 200, 那么首先把 128 以下的部分 (72, 0x48) 弄出来放到一个字节里, 再把高位 (200 / 128 =) 1 放到较低位置 (顺带吐槽, 这里又成了小端了), 于是就成了 0x01 0x48, 最后把前面的字节的首位都转成 1, 就成了 0x81 0x48, 如果更大, 可能要用到 3 个字节, 比如 40000 (= 0x02 * 128*128 + 0x38 * 128 + 0x40), 就表示为 0x02 0x38 0x40, 再转换首位变为 0x82 0xb8 0x40. 不过实际上用到这么大数字机会不多.

    时间数据结束之后, 后面便是 1 字节事件类型数据. 它的高 4 位表示指令类型

  • 8 : 发声停止
  • 9 : 发声开始
  • a : 触后音符
  • b : 控制器转换
  • c : 音色 (乐器) 转换
  • d : 触后通道
  • e : 音高设置

(其实后面的 abde 我都不太清楚是啥, 先整理进来)
    其低 4 位是通道号 (至多 16 个通道). 在 MIDI 输出时, 一个通道同时只能产生一种音色的声音, 切换音色后, 该通道发声就会持续地改变.
    指令决定了其后事件参数的数据长度, 比如发声开始 (0x90) 后面跟 2 字节数据, 第一字节表示音调, 第二字节表示弹奏力度 (貌似音量); 而乐器设置 (0xe0) 后只需 1 字节 (0~127), 表示转换的乐器种类编号.

实例

    纸上谈兵无益, 先来个具体的例子. 由于 MIDI 是二进制文件, 产生它需要搞点小技巧. Shell 中的 xxd 是个不错的选择, 它能将输入的十六进制数变成二进制数据, 如

$ echo -n "61 62 63 64" | xxd -r -p

    接下来将 MIDI 格式数据输入文件中时将采用类似下面的方法

$ cat | xxd -r -p > output.midi
Place MIDI data here

    或者将数据保存到文本, 并

$ xxd -r -p input-file > output.midi

(Windows 同学请找靠谱二进制编辑器或者安装 cygwin / mingw 之类的)
    下面是一个单音轨 MIDI 例子, 音轨上只有一个音符

4d546864 00000006 0001 0001 0060
4d54726b 0000000d
05 90 6040
8148 80 6040
00ff2f00

第一行数据是 MIDI 文件头以及音轨信息;
接下来一行则是第一音轨头, 以及音轨数据长度 (共 13 字节);
05 90… 这行是一次发声, 从 05 tick 开始;
8148 80… 这行是发声结束, 8148 即上面演算过, 用来表示 200 tick 的时间值;
最后 00ff2f00 是音轨结束.
    (在 Linux 播放 MIDI 内容看这里)

    接下来尝试在音轨中更换乐器, 然后再次发声

4d546864 00000006 0001 0001 0060
4d54726b 00000019
05 90 6040
8148 80 6040
00 c0 40
05 90 6040
8148 80 6040
00ff2f00

00 c0 40 这一行在上一次演奏结束后立即将乐器更换为 0x40 号乐器 (萨克斯风, 关于乐器编号可以在这里看到, 该编号从 1 开始, 因此需要减一得到实际写入文件的编号), 然后再次演奏. 因此听到的两次乐声明显不同.

    下面是一个二音轨的例子, 其中第一个音轨仍以钢琴声演奏, 而第二音轨则是以萨克斯风演奏 (通过 0x1 通道)

4d546864 00000006 0001 0002 0060

4d54726b 0000000d
05 90 6040
8148 80 6040
00ff2f00

4d54726b 00000010
00 c1 40
70 91 6040
8148 81 6040
00ff2f00

from Bit Focus: http://blog.bitfoc.us/?p=495

Written by cwyalpha

九月 5, 2012 在 12:42 下午

发表在 Uncategorized

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: