midiparser.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #!/usr/bin/python
  2. from .midimessage import *
  3. class MIDITrack:
  4. def __init__(self):
  5. pass
  6. class InputStreamWrapper:
  7. def __init__(self, fd, mode="rb"):
  8. if isinstance(fd, str):
  9. fd=open(fd, mode)
  10. self.fd=fd
  11. self.buffer=[]
  12. self.n=0
  13. self.offset=0
  14. self.doRecord=False
  15. def close(self):
  16. self.fd.close()
  17. def read(self, n=-1):
  18. x=self.fd.read(n)
  19. self.offset+=n
  20. if self.doRecord: self.buffer+=x
  21. return x
  22. def record(self, rec=True):
  23. self.doRecord=rec
  24. class MIDIChunk:
  25. def __init__(self, fd):
  26. self.fd=fd
  27. self.magick=""
  28. self.length=0
  29. def parse(self):
  30. pass
  31. def print(self):
  32. pass
  33. def readMagick(self):
  34. return self.fd.read(4).decode('ascii')
  35. def reverseMsb(self, x):
  36. out=0
  37. n=len(x)
  38. for i in reversed(range(n)):
  39. out+= (x[i]<< (8*(n-1-i)))
  40. return out
  41. def intN(self, n):
  42. return self.reverseMsb(self.fd.read(n))
  43. def int4(self):
  44. return self.intN(4)
  45. def int2(self):
  46. return self.intN(2)
  47. def byte(self):
  48. return self.intN(1)
  49. def skip(self, n=1):
  50. self.fd.read(n)
  51. class MIDIHeaderChunk(MIDIChunk):
  52. def __init__(self, fd):
  53. MIDIChunk.__init__(self,fd)
  54. self.format=0
  55. self.tracks=0
  56. self.division=0
  57. MIDIHeaderChunk.FORMAT_SINGLE_CHANNEL=0
  58. MIDIHeaderChunk.FORMAT_MULTIPLE_CHANNEL=1
  59. MIDIHeaderChunk.FORMAT_MULTIPLE_INDEPENDANT_CHANNEL=2
  60. self.variableTicks=False
  61. self.ticksPerQuarter=0 # beat per quarter note
  62. def parse(self):
  63. f=self.fd
  64. self.magick=self.readMagick()
  65. if self.magick!="MThd":
  66. raise MIDIParserException("Bad header magick number")
  67. self.length=self.int4()
  68. self.format=self.int2()
  69. self.tracks=self.int2()
  70. self.division=self.int2()
  71. self.variableTicks=True if self.division & 0x8000 else False
  72. if not self.variableTicks:
  73. self.ticksPerQuarter= self.division & 0x7fff
  74. class MIDITrackChunk(MIDIChunk):
  75. def __init__(self, fd, skipTrack=False):
  76. MIDIChunk.__init__(self,fd)
  77. self.time=0
  78. self.events=[]
  79. self.skipTrack=skipTrack
  80. def readDeltaTime(self):
  81. out=[]
  82. while True:
  83. x=self.byte()
  84. out.append(x&0x7F)
  85. if not (x&0x80):
  86. break
  87. n=len(out)
  88. ret=0
  89. for i in range(n):
  90. j=n-i-1
  91. ret+= ( (out[i]) <<(j*7) )
  92. self.time+=ret
  93. return ret
  94. def parse(self):
  95. f=self.fd
  96. self.magick=self.readMagick()
  97. if self.magick!="MTrk":
  98. raise MIDIParserException("Bad track magick number")
  99. self.length=self.int4()
  100. events=[]
  101. if self.skipTrack: self.fd.read(self.length)
  102. else:
  103. while True:
  104. delta=self.readDeltaTime()
  105. evt=self.readEvent()
  106. events.append((self.time, evt))
  107. if evt.isEndTrack():
  108. break
  109. return events
  110. def readEvent(self):
  111. return MidiMessage.parse(self.fd)
  112. class MidiFile:
  113. def __init__(self, path):
  114. self.path=path
  115. self.tracksCount=0
  116. self.header=None
  117. self.tracks=[]
  118. self.loaded=False
  119. def parse(self):
  120. self.fd=0
  121. try:
  122. self.fd=open(self.path, "rb")
  123. except Exception as e:
  124. ERR(type(e).__name__+" : "+str(e))
  125. return e
  126. self.header=MIDIHeaderChunk(self.fd)
  127. self.header.parse()
  128. for t in range(self.header.tracks):
  129. track=MIDITrackChunk(self.fd)
  130. self.tracks.append(track.parse())
  131. self.fd.close()
  132. self.loaded=True
  133. def isLoaded(self):
  134. return self.loaded
  135. if __name__=="__main__":
  136. #path="/home/fanch/Documents/tabs/Johann Pachelbel - Canon In D (ver 6 by Ezechiel).mid"
  137. path="/home/fanch/Documents/tabs/Misc Traditional - Katyusha.mid"
  138. m=MidiFile(path)
  139. m.parse()