#!/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()