MIDIファイルを生成する
Python3は文字列とかバイト列とかの扱いがすっきりした感じがするので練習がてらStandardMIDIFileを出力させてみた.
:::python
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
import copy
import heapq
class SMF(object):
'''Standard MIDI File
'''
def __init__(self, fmt=1, division=480):
self.division = division
self.format = fmt
self.tracks = []
def measuretick(self, division):
return divmod(abs_time, division * 4)
def abs_time(self, measure, tick):
return measure * 4 + tick
def __bytes__(self):
# header signature
result = bytearray(b"MThd")
# header size
result += (6).to_bytes(4, byteorder='big')
# format
result += self.format.to_bytes(2, byteorder='big')
# number of tracks
if self.format == 0:
result += (1).to_bytes(2, byteorder='big')
elif self.format == 1:
result += len(self.tracks).to_bytes(2, byteorder='big')
# time division
result += self.division.to_bytes(2, byteorder='big')
if self.format == 0:
sumtrack = Track()
for t in self.tracks:
sumtrack.events += t.events
result += bytes(sumtrack)
elif self.format == 1:
for t in self.tracks:
result += bytes(t)
else:
raise ValueError("format is invalid: %d".format(self.format))
return bytes(result)
class Track(object):
'''Track data
'''
def __init__(self):
self.events = []
def to_vbytes(self, dt):
'''convert to the variable length format.
'''
result = [dt % 0x80]
dt //= 0x80
while dt > 0:
result += [dt % 0x80 | 0x80]
dt //= 0x80
return bytes(result[::-1])
def _block_bytes(self):
result = bytearray()
eventheap = copy.deepcopy(self.events)
heapq.heapify(eventheap)
t = 0
runninng = None
while len(eventheap) > 0:
e = heapq.heappop(eventheap)
delta = e.abs_time - t
t = e.abs_time
result += self.to_vbytes(delta)
eventbytes = bytes(e)
if runninng == eventbytes[0] and runninng != b"\xF0" and runninng != b"\xFF":
result += eventbytes[1:]
else:
result += eventbytes
runninng = eventbytes[0]
if e.has_after():
heapq.heappush(eventheap, e.after())
# track end
result += b"\x00\xFF\x2F\x00"
return result
def __bytes__(self):
# track signature
result = bytearray(b"MTrk")
blocks = self._block_bytes()
# block size
result += len(blocks).to_bytes(4, byteorder="big")
result += blocks
return bytes(result)
class Event(object):
def __init__(self, t=0):
self.abs_time = t
def __lt__(self, o):
return self.abs_time < o.abs_time
def __le__(self, o):
return self.abs_time <= o.abs_time
def __gt__(self, o):
return self.abs_time > o.abs_time
def __ge__(self, o):
return self.abs_time >= o.abs_time
def has_after(self):
return hasattr(self, "after")
# SysEx Events
class GMSystemOn(Event):
def __bytes__(self):
return b"\xF0\x05\x7E\x7F\x09\x01\xF7"
# Meta Events
class Tempo(Event):
def __init__(self, tempo=120, t=0):
Event.__init__(self, t)
if tempo <= 0:
tempo = 120
self.microsec = 60 * 1000000 // tempo
def __bytes__(self):
return b"\xFF\x51\x03" + self.microsec.to_bytes(3, "big")
# MIDI Events
class ControllChange(Event):
def __init__(self, t, ch, d):
Event.__init__(self, t)
self.ch = ch
self.d = d
def __bytes__(self):
return bytes([0xB0 | self.ch, self.ccnum, self.d])
class BankSelectMSB(ControllChange):
ccnum = 0x00
class Modulation(ControllChange):
ccnum = 0x01
class BreathController(ControllChange):
ccnum = 0x02
class FootController(ControllChange):
ccnum = 0x04
class Portamento(ControllChange):
ccnum = 0x05
class DataEntryMSB(ControllChange):
ccnum = 0x06
class Volume(ControllChange):
ccnum = 0x07
class Balance(ControllChange):
ccnum = 0x08
class Pan(ControllChange):
ccnum = 0x0a
class Expression(ControllChange):
ccnum = 0x0b
class Hold(ControllChange):
ccnum = 0x40
class Portamento(ControllChange):
ccnum = 0x41
class Sostenuto(ControllChange):
ccnum = 0x42
class Soft(ControllChange):
ccnum = 0x43
class Resonance(ControllChange):
ccnum = 0x47
class Release(ControllChange):
ccnum = 0x48
class Atack(ControllChange):
ccnum = 0x49
class CutOff(ControllChange):
ccnum = 0x4a
class NoteOff(Event):
def __init__(self, t, ch, n):
Event.__init__(self, t)
self.ch = ch
self.n = n
def __bytes__(self):
# use 0x90 for runninng status
return bytes([0x90 | self.ch, self.n, 0x00])
class NoteOn(Event):
def __init__(self, t, ch, n, vel, gate):
Event.__init__(self, t)
self.ch = ch
self.n = n
self.vel = vel
self.gate = gate
def __bytes__(self):
return bytes([0x90 | self.ch, self.n, self.vel])
def after(self):
return NoteOff(self.abs_time + self.gate, self.ch, self.n)
class ProgramChange(Event):
def __init__(self, t, ch, p):
Event.__init__(self, t)
self.ch = ch
self.p = p
def __bytes__(self):
return bytes([0xC0 | self.ch, self.p])
import unittest
class SMFTest(unittest.TestCase):
def test_smf_bytes(self):
f = SMF()
result = bytes(f)
print(result)
self.assertIsInstance(result, bytes)
self.assertEqual(result[:4], b"MThd")
print(int.from_bytes(result[4:8], "big"))
class TrackTest(unittest.TestCase):
def test_to_vbytes(self):
t = Track()
self.assertEqual(t.to_vbytes(0x00000000), b"\x00")
self.assertEqual(t.to_vbytes(0x00000040), b"\x40")
self.assertEqual(t.to_vbytes(0x0000007F), b"\x7F")
self.assertEqual(t.to_vbytes(0x00000080), b"\x81\x00")
self.assertEqual(t.to_vbytes(0x00002000), b"\xC0\x00")
self.assertEqual(t.to_vbytes(0x00003FFF), b"\xFF\x7F")
self.assertEqual(t.to_vbytes(0x00004000), b"\x81\x80\x00")
self.assertEqual(t.to_vbytes(0x00100000), b"\xC0\x80\x00")
self.assertEqual(t.to_vbytes(0x001FFFFF), b"\xFF\xFF\x7F")
self.assertEqual(t.to_vbytes(0x00200000), b"\x81\x80\x80\x00")
self.assertEqual(t.to_vbytes(0x08000000), b"\xC0\x80\x80\x00")
self.assertEqual(t.to_vbytes(0x0FFFFFFF), b"\xFF\xFF\xFF\x7F")
if __name__ == '__main__':
# unittest.main()
smf = SMF(fmt=1)
smf.tracks.append(Track())
smf.tracks[0].events.append(GMSystemOn(0))
smf.tracks[0].events.append(Tempo(tempo=120, t=40))
smf.tracks.append(Track())
ch = 1
smf.tracks[1].events.append(Volume(40, ch, 96))
smf.tracks[1].events.append(ProgramChange(60, ch, 0x39))
smf.tracks[1].events.append(NoteOn(0 + 480 * 4, ch, 0x3C, 127, 480))
smf.tracks[1].events.append(NoteOn(0 + 480 * 4, ch, 0x44-12, 127, 480))
smf.tracks[1].events.append(NoteOn(480 + 480 * 4, ch, 0x3E, 127, 480))
smf.tracks[1].events.append(NoteOn(480 + 480 * 4, ch, 0x45-12, 127, 480))
print("write to test.mid...")
with open("test.mid", "wb") as f:
f.write(bytes(smf))
print("OK!")
参考