如何使用 python 从头开始编写 Midi 文件
How to write Midi File from scratch using python
我正在研究 Midi 文件规范,现在我正在测试它,如果由 Timidity 播放它可以正常工作,但它对于任何一个 Garage Band 都已损坏,OS X(输出不播放)和 Synthesia。
head = '4d 54 68 64'
chunklen = '00 00 00 06'
mformat = '00 01'
ntracks = '00 02'
tickdiv = '00 60'
trackid = '4d 54 72 6b'
eot = '00 ff 2f 00'
makeheader = lambda : " ".join([head,chunklen,mformat,ntracks,tickdiv])
def chunklencalc(notes):
chlen = format(len(notes)*4, 'x')
return " ".join([x for x in re.compile('(.{2})').split("00000000"[len(chlen):] + chlen) if x != ''])
maketrack = lambda notes : " ".join([trackid, chunklencalc(notes)] + notes + [eot])
makestandardquarter = lambda root : f"00 90 {root} 64 60 80 {root} 64"
def createMidi(filename,bytelist):
with open(filename, 'wb') as f:
for e in bytelist.split(" "):
f.write(bytes.fromhex(e))
filename = 'firsttest.mid'
head = makeheader()
notes1 =[
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
]
notes2 =[
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
]
track1 = maketrack(notes1)
track2 = maketrack(notes2)
createMidi(filename, " ".join([head, track1,track2]))
我期待在两个曲目中有一系列的季度,只有一个曲目只得到前四个。
在深入了解 hexdump 并查看定义的块长度之后:
第一个块声明为 0x20
(32) 字节长,从位置 0x17
(23) 开始并在 0x5b
(91) 结束,这意味着您的块长度计算偏离了 34 个字节.
00000000 4d 54 68 64 00 00 00 06 00 01 00 02 00 60 4d 54 |MThd.........`MT|
00000010 72 6b 00 00 00 20 00 90 3c 64 60 80 3c 64 00 90 |rk... ..<d`.<d..|
00000020 3c 64 60 80 3c 64 00 90 3c 64 60 80 3c 64 00 90 |<d`.<d..<d`.<d..|
*
00000050 3c 64 60 80 3c 64 00 ff 2f 00 4d 54 72 6b 00 00 |<d`.<d../.MTrk..|
00000060 00 20 00 90 40 64 60 80 40 64 00 90 40 64 60 80 |. ..@d`.@d..@d`.|
00000070 40 64 00 90 40 64 60 80 40 64 00 90 40 64 60 80 |@d..@d`.@d..@d`.|
*
000000a0 40 64 00 ff 2f 00 |@d../.|
000000a6
我使用结构编写了自己的版本:
import struct
HEAD_ID = b"\x4d\x54\x68\x64"
TRACK_ID = b"\x4d\x54\x72\x6b"
class HeaderChunk:
def __init__(self, format, ntrack, tickdiv):
self.format = format
self.ntrack = ntrack
self.tickdiv = tickdiv
def dump(self):
payload = struct.pack(">HH2s", self.format, self.ntrack, self.tickdiv)
header = HEAD_ID + struct.pack(">I", len(payload))
return header + payload
class TrackChunk:
"""Represents a track"""
def __init__(self):
self.data = b""
def quarter(self, note):
self.data += b"\x00\x90" + note + b"\x64\x60\x80" + note + b"\x64"
def dump(self):
header = TRACK_ID + struct.pack(">I", len(self.data))
return header + self.data
header = HeaderChunk(1, 2, b"\x00\x60")
first_track = TrackChunk()
for _ in range(8):
first_track.quarter(b"\x3c")
second_track = TrackChunk()
for _ in range(8):
second_track.quarter(b"\x40")
with open("joac-example.mid", "wb") as output:
output.write(header.dump())
output.write(first_track.dump())
output.write(second_track.dump())
它已正确加载到 garage band
我正在研究 Midi 文件规范,现在我正在测试它,如果由 Timidity 播放它可以正常工作,但它对于任何一个 Garage Band 都已损坏,OS X(输出不播放)和 Synthesia。
head = '4d 54 68 64'
chunklen = '00 00 00 06'
mformat = '00 01'
ntracks = '00 02'
tickdiv = '00 60'
trackid = '4d 54 72 6b'
eot = '00 ff 2f 00'
makeheader = lambda : " ".join([head,chunklen,mformat,ntracks,tickdiv])
def chunklencalc(notes):
chlen = format(len(notes)*4, 'x')
return " ".join([x for x in re.compile('(.{2})').split("00000000"[len(chlen):] + chlen) if x != ''])
maketrack = lambda notes : " ".join([trackid, chunklencalc(notes)] + notes + [eot])
makestandardquarter = lambda root : f"00 90 {root} 64 60 80 {root} 64"
def createMidi(filename,bytelist):
with open(filename, 'wb') as f:
for e in bytelist.split(" "):
f.write(bytes.fromhex(e))
filename = 'firsttest.mid'
head = makeheader()
notes1 =[
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
makestandardquarter('3c'),
]
notes2 =[
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
makestandardquarter('40'),
]
track1 = maketrack(notes1)
track2 = maketrack(notes2)
createMidi(filename, " ".join([head, track1,track2]))
我期待在两个曲目中有一系列的季度,只有一个曲目只得到前四个。
在深入了解 hexdump 并查看定义的块长度之后:
第一个块声明为 0x20
(32) 字节长,从位置 0x17
(23) 开始并在 0x5b
(91) 结束,这意味着您的块长度计算偏离了 34 个字节.
00000000 4d 54 68 64 00 00 00 06 00 01 00 02 00 60 4d 54 |MThd.........`MT|
00000010 72 6b 00 00 00 20 00 90 3c 64 60 80 3c 64 00 90 |rk... ..<d`.<d..|
00000020 3c 64 60 80 3c 64 00 90 3c 64 60 80 3c 64 00 90 |<d`.<d..<d`.<d..|
*
00000050 3c 64 60 80 3c 64 00 ff 2f 00 4d 54 72 6b 00 00 |<d`.<d../.MTrk..|
00000060 00 20 00 90 40 64 60 80 40 64 00 90 40 64 60 80 |. ..@d`.@d..@d`.|
00000070 40 64 00 90 40 64 60 80 40 64 00 90 40 64 60 80 |@d..@d`.@d..@d`.|
*
000000a0 40 64 00 ff 2f 00 |@d../.|
000000a6
我使用结构编写了自己的版本:
import struct
HEAD_ID = b"\x4d\x54\x68\x64"
TRACK_ID = b"\x4d\x54\x72\x6b"
class HeaderChunk:
def __init__(self, format, ntrack, tickdiv):
self.format = format
self.ntrack = ntrack
self.tickdiv = tickdiv
def dump(self):
payload = struct.pack(">HH2s", self.format, self.ntrack, self.tickdiv)
header = HEAD_ID + struct.pack(">I", len(payload))
return header + payload
class TrackChunk:
"""Represents a track"""
def __init__(self):
self.data = b""
def quarter(self, note):
self.data += b"\x00\x90" + note + b"\x64\x60\x80" + note + b"\x64"
def dump(self):
header = TRACK_ID + struct.pack(">I", len(self.data))
return header + self.data
header = HeaderChunk(1, 2, b"\x00\x60")
first_track = TrackChunk()
for _ in range(8):
first_track.quarter(b"\x3c")
second_track = TrackChunk()
for _ in range(8):
second_track.quarter(b"\x40")
with open("joac-example.mid", "wb") as output:
output.write(header.dump())
output.write(first_track.dump())
output.write(second_track.dump())
它已正确加载到 garage band