确定 MIDI 文件的时间分割

Determining time division of a MIDI file

我正在 Python 中编写一个脚本来解析 MIDI 文件(是的,我知道 Python 存在 MIDI 解析库,但对于我的用例来说,如果我从头开始制作它是最简单的)。

我遇到的一件事是时分。 header 的最后两个字节指定了时间划分,但我无法确定文件的时间划分是以每拍滴答数还是每秒帧数表示的。在做了一些阅读之后,似乎最高字节的最高位指示时间划分在两个中的哪一个。我感到困惑的是,如果一个字节的最高位是一个字节的第一位还是最后一位一个字节的位,以及如何完全读取 MIDI 时间划分。

编辑:例如,我拥有的 MIDI 文件的 header 如下:

4d54 6864 0000 0006 0000 0001 0078

0078 are the two bytes that denote the time sig, but I am confused as how to interpret it.

编辑 2:

def openmidi(file):
    tmbr = []
    f = open(file, "rb")#opening the midi in binary mode
    loopfile = True
    while loopfile == True:
        cb = f.read(1)
        if cb != b'':#checking if there are still bytes left to read
            tmbr.append(cb)
        else:
            loopfile = False
    return tmbr

def byteread(num):#will read and return the specified number of bytes
    global bytecounter
    bytehold = b''
    for i in range(0, num):#reads specified number of bytes
        bytehold+=midibytearray[i+bytecounter]#number of increment plus the read position
    bytecounter+=num#after reading is done read position is incremented by the number of bytes read.
    return bytehold#after looping is done the specified bytes are returned.

def timetype(deltatimebytes):#used to determine if the time division is in ticks per beat or frames per second.
    if str(deltatimebytes).replace("b'","").replace("'","")[0:2] == "00":
        return True#if true the time division is in ticks per beat.
    else:
        return False#the time division is in frames per second.


global bytecounter
bytecounter = 0 #keeps track of what position in the file is being read.

midibytearray = openmidi("C:\Users\gabep\Desktop\Electrorchestrion\Midis\BONEY M.Rasputin K.mid") #array that the bytes will be stored in.



header = byteread(4)
chunklength = byteread(4)
formattype = byteread(2)
numofmtrkchunks = byteread(2)
deltatime = byteread(2)#if no tempo is assigned, 120bpm is assumed.
print(deltatime)

print("Header: "+str(header.decode("utf-8")))
print("MThd chunk length: "+str(int(chunklength.hex(), 16)))
print("Midi Format Type: "+str(int(formattype.hex(), 16)))
print("Number of MTrk chunks (number of tracks): "+str(int(numofmtrkchunks.hex(), 16)))



print("Delta time: "+str(int(deltatime.hex(), 16)))
if timetype(deltatime.hex()) == True:
    print("Time signature is in ticks per beat")
else:
    print("Time signature is in frames per second")

也许你不知道,官方的MIDI规范已经有了,你可以免费download the document。 (您需要先注册为网站用户)。它包括详细的 SMF 格式。

这是头块的描述。


文件开头的头块指定了文件中数据的一些基本信息。这是完整块的语法:

<Header Chunk> = <chunk type> <length> <format> <ntrks> <division>

如上所述,<chunk type>是四个ASCII字符'MThd'; <length> 是数字 6 的 32 位表示(高字节在前)。 数据部分包含三个 16 位字,首先存储最高有效字节。 第一个词 <format> 指定文件的整体组织。仅指定了三个 <format> 的值:

0=文件包含单个多声道轨道

1=文件包含一个或多个同步轨道(或 MIDI 输出) 序列

2=文件包含一个或多个顺序独立的单轨模式 下面提供了有关这些格式的更多信息。

下一个词,<ntrks>,是文件中轨道块的数量。对于格式 0 的文件,它将始终为 1。

第三个字<division>,说明delta-times的意思。它有两种格式,一种用于公制时间,一种用于基于时间码的时间:

  |bits                                        |
  |15|14        ...         8|7     ...      0 |
  |--|-----------------------|-----------------|
  | 0|         ticks per quarter-note          |
  | 1| negative SMPTE format | ticks per frame |

如果 <division> 的位 15 为零,则位 14 到 0 表示构成四分音符的增量时间 "ticks" 的数量。例如,如果 <division> 为 96,则文件中两个事件之间八分音符的时间间隔为 48。如果 <division> 的第 15 位为一,则文件中的增量时间对应于秒的细分,以与 SMPTE 和 MIDI 时间码一致的方式。第 14 到 8 位包含四个值 -24、-25、-29 或 -30 之一,对应于四种标准 SMPTE 和 MIDI 时间码格式(-29 对应于 30 丢帧),表示帧数每秒。这些负数以二进制补码形式存储。第二个字节(存储正数)是一帧内的分辨率:典型值可能是 4(MIDI 时间码分辨率), 8、10、80(位分辨率)或 100。此系统允许精确指定基于时间码的轨道,但也允许通过指定 25 frames/sec 和每帧 40 个单位的分辨率来基于毫秒的轨道。如果文件中的事件以 30 帧时间码的位分辨率存储,则除法字将为十六进制的 E250。


在您的示例中,您的第三个单词(十六进制 0078)表示 <division> 是每四分音符 120 个节拍。

增量时间以文件中事件的滴答为单位给出。时间签名是另一个完全不同的东西。它是节奏的指示,是一种元事件类型。 (参见规范第 10 页)。