#python #hex #midi
#python #hex #midi
Вопрос:
Я изучаю спецификацию 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]))
Я ожидал серию четвертей в двух дорожках, получил только первые четыре только на одной дорожке.
Комментарии:
1. вы проверили, имеет ли файл правильную конечность? глядя на спецификацию, она должна быть маленькой.
2. Меня беспокоит вычисление chunklen и функции, которая создает заметку, потому что в предыдущем тесте я вручную рассчитал и жестко запрограммировал ноты, и это работает в каждой системе. Также кажется странным, что обрабатывается только половина нот. Наконец, я не знаком с такого рода низкоуровневыми материалами, любая помощь приветствуется.
3. Также в функции chunklen, я думаю, должно быть 4 для сегмента eot (4 байта), но робость не жалуется.
4. для работы с байтами я счел полезным использовать модуль struct python: docs.python.org/3/library/struct.html Помните, что вы можете определить двоичные строки следующим образом:
b"x00"
Ответ №1:
После глубокого изучения 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
Я написал свою собственную версию, используя struct:
import struct
HEAD_ID = b"x4dx54x68x64"
TRACK_ID = b"x4dx54x72x6b"
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"x00x90" note b"x64x60x80" note b"x64"
def dump(self):
header = TRACK_ID struct.pack(">I", len(self.data))
return header self.data
header = HeaderChunk(1, 2, b"x00x60")
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