123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- #!/usr/bin/python
- from .midimessage import *
- class MIDITrack:
- def __init__(self):
- pass
- class InputStreamWrapper:
- def __init__(self, fd, mode="rb"):
- if isinstance(fd, str):
- fd=open(fd, mode)
- self.fd=fd
- self.buffer=[]
- self.n=0
- self.offset=0
- self.doRecord=False
-
- def close(self):
- self.fd.close()
-
- def read(self, n=-1):
- x=self.fd.read(n)
- self.offset+=n
- if self.doRecord: self.buffer+=x
- return x
-
- def record(self, rec=True):
- self.doRecord=rec
- def littleEndian(x, n):
- b=bytes()
- for i in range(n):
- b+=bytes([(x >> (8 * (n - 1 - i))) & 0xff])
- return b
- class MIDIChunk:
- def __init__(self, fd):
- self.fd=fd
- self.magick=""
- self.length=0
-
- def parse(self):
- pass
-
- def print(self):
- pass
- def write(self, fd):
- pass
-
- def readMagick(self):
- return self.fd.read(4).decode('ascii')
-
- def reverseMsb(self, x):
- out=0
- n=len(x)
- for i in reversed(range(n)):
- out+= (x[i]<< (8*(n-1-i)))
- return out
-
- def intN(self, n, x=None):
- if x!=None:
- for i in range(n):
- self.fd.write(bytes([ (x>>(8*(n-1-i)) )& 0xff]))
- else: return self.reverseMsb(self.fd.read(n))
-
- def int4(self, x=None):
- self.intN(4,x)
-
- def int2(self, x=None):
- return self.intN(2,x)
-
- def byte(self, x=None):
- return self.intN(1,x)
-
- def skip(self, n=1):
- self.fd.read(n)
- class MIDIHeaderChunk(MIDIChunk):
- def __init__(self, fd=None):
- MIDIChunk.__init__(self,fd)
- self.format=0
- self.tracks=0
- self.division=0
- MIDIHeaderChunk.FORMAT_SINGLE_CHANNEL=0
- MIDIHeaderChunk.FORMAT_MULTIPLE_CHANNEL=1
- MIDIHeaderChunk.FORMAT_MULTIPLE_INDEPENDANT_CHANNEL=2
-
- self.variableTicks=False
- self.ticksPerQuarter=0 # beat per quarter note
- def write(self, fd):
- self.fd=fd
- self.fd.write("MThd".encode())
- self.int4(self.length)
- self.int2(self.format)
- self.int2(self.tracks)
- self.int2(self.division)
- def parse(self):
- f=self.fd
- self.magick=self.readMagick()
- if self.magick!="MThd":
- raise MIDIParserException("Bad header magick number")
- self.length=self.int4()
- self.format=self.int2()
- self.tracks=self.int2()
- self.division=self.int2()
-
- self.variableTicks=True if self.division & 0x8000 else False
- if not self.variableTicks:
- self.ticksPerQuarter= self.division & 0x7fff
- class MIDITrackChunk(MIDIChunk):
- def __init__(self, fd=None, skipTrack=False):
- MIDIChunk.__init__(self,fd)
- self.time=0
- self.events=[]
- self.skipTrack=skipTrack
-
- def readDeltaTime(self):
- out=[]
- while True:
- x=self.byte()
- out.append(x&0x7F)
- if not (x&0x80):
- break
- n=len(out)
- ret=0
- for i in range(n):
- j=n-i-1
- ret+= ( (out[i]) <<(j*7) )
-
- self.time+=ret
- return ret
- def write(self, fd):
- self.fd=fd
- self.fd.write("MTrk".encode())
- self.int4(self.length)
- b=bytes()
- count=0
- for evt in self.events:
- e=littleEndian(int(evt[0]*960),1)+bytes([0xff])+bytes(evt[1].bytes())
- count+=len(e)
- print(evt[1].bytes())
- print(bytes(evt[1].bytes()))
- count+=evt[0]
- b+=e
- print(b)
- b+=bytes([0])+bytes(EndOfTrack().bytes())
- print(b)
- self.fd.write(b)
- def addEvent(self, time, evt):
- self.events.append((time, evt))
- def addNote(self, time, note, channel=0):
- non= NoteOn(channel)
- non.key=note
- non.velocity=0x7f
- self.events.append((self.time,non))
- nof= NoteOff(channel)
- nof.key=note
- self.events.append((self.time+time*960,nof))
- self.time+=time*960
- print(time*960)
- def parse(self):
- f=self.fd
- self.magick=self.readMagick()
- if self.magick!="MTrk":
- raise MIDIParserException("Bad track magick number")
- self.length=self.int4()
-
- events=[]
- if self.skipTrack: self.fd.read(self.length)
- else:
- while True:
- delta=self.readDeltaTime()
- evt=self.readEvent()
-
- events.append((self.time, evt))
- if evt.isEndTrack():
- break
- self.events=events
- return events
-
-
- def readEvent(self):
- return MidiMessage.parse(self.fd)
-
- class MidiFile:
- def __init__(self, path=None):
- if path:
- self.path=path
- self.tracksCount=0
- self.header=None
- self.tracks=[]
- self.loaded=False
- self.events=[]
-
- def parse(self):
- self.fd=0
- try:
- self.fd=open(self.path, "rb")
- except Exception as e:
- ERR(type(e).__name__+" : "+str(e))
- return e
- self.header=MIDIHeaderChunk(self.fd)
- self.header.parse()
- for t in range(self.header.tracks):
- track=MIDITrackChunk(self.fd)
-
- self.tracks.append(track.parse())
- self.fd.close()
- self.loaded=True
-
- def isLoaded(self):
- return self.loaded
-
- if __name__=="__main__":
- #path="/home/fanch/Documents/tabs/Johann Pachelbel - Canon In D (ver 6 by Ezechiel).mid"
- path="/home/fanch/Documents/tabs/Misc Traditional - Katyusha.mid"
- m=MidiFile(path)
- m.parse()
|