#!/usr/bin/python from .options import * import time import io class MIDIParserException(Exception): def __init__(self, message): super().__init__(message) def send_message(x, data): s="[" for i in range(len(data)): if i>0: s+=", " s+=hex(data[i]) MIDIDEBUG(s+"]") x.send_message(data) class MidiType: def __init__(self): MidiType.ERROR=0 MidiType.NOTE_OFF=0x80 MidiType.NOTE_ON=0x90 MidiType.KEY_PRESSURE=0xA0 MidiType.CONTROL_CHANGE=0xB0 MidiType.PROGRAM_CHANGE=0xC0 MidiType.CHANNEL_PRESSURE=0xD0 MidiType.PITCH_WHEEL_CHANGE=0xE0 MidiType.SYSTEM_EXCLUSIVE=0xF0 MidiType.SONG_POSITION_POINTER=0xF2 MidiType.SONG_SELECT=0xF2 MidiType.TUNE_REQUEST=0xF6 MidiType.END_OF_EXCUSIVE=0xF7 #not used MidiType.TIMING_CLOCK=0xF8 # a faire MidiType.START=0xFA MidiType.CONTINUE=0xFB MidiType.STOP=0xFC MidiType.ACTIVE_SENSING=0xFE MidiType.META=0xFF MidiType.RESET=0xFF MidiType.META_SEQ=0x00 MidiType.META_TEXT=0x01 MidiType.META_COPYRIGHT=0x02 MidiType.META_TRACK_NAME=0x03 MidiType.META_INSTRUMENT_NAME=0x04 MidiType.META_LYRICS=0x05 MidiType.META_TEXT_MARKER=0x06 MidiType.META_CUE_POINT=0x07 MidiType.META_CHANNEL_PREFIX=0x20 MidiType.META_END_OF_TRACK=0x2F MidiType.META_SET_TEMPO=0x51 MidiType.META_SMPTE_OFFSET=0x54 MidiType.META_TIME_SIGNATURE=0x58 MidiType.META_KEY_SIGNATURE=0x59 MidiType.META_SPECIFIC=0x7F MidiType() class MidiMessage: def __init__(self, typ): self.type=typ self.__sleep=0.00001 self.__doSleep=True self.key=-1 def isEndTrack(self): return False def getType(self): if self.type>=0x80 and self.type<=0xE0: return self.type&0xf0 return self.type def isTypeOf(self, x): return self.getType() == x def isMeta(self): return self.type>=0 and self.type<=0x58 def isSysEx(self): return self.type>=0xF0 and self.type<=0xF7 def write(self, oport): send_message(oport, self.bytes()) #oport.send_message(self.bytes()) if self.__doSleep: time.sleep(self.__sleep) def read(self, iport, n=1): if n>0: out=[] for i in range(n): out.append(self.byte()) return out return iport.get_message() def syncRead(self, iport): x=None while True: x=self.read(iport) if x!=None: return x def __eq__(self, x): xx=x.bytes() ex=self.bytes() if len(xx) != len(ex): return False for i in len(xx): if xx[i] != ex[i]: return False return True def transpose(self, n): pass def copy(self): x=MidiMessage.parse(self.bytes()) return x @staticmethod def byte(fd): vv=fd.read(1) if vv==None or len(vv)==0: raise MIDIParserException("End off file premature") return int(vv[0]) @staticmethod def parse(fd): if isinstance(fd, (bytes, list)): b=bytes(fd) fd=io.BytesIO(b) return MidiMessage.parseStream(fd) @staticmethod def parseStream(fd): vv=fd.read(1) if vv==None: return MidiEndFile() first=int(vv[0]) xa= first&0xf0 xb= first&0x0f if xa==MidiType.NOTE_ON: return NoteOn(xb, fd) elif xa==MidiType.NOTE_OFF: return NoteOff(xb, fd) elif xa==MidiType.KEY_PRESSURE: return KeyPressure(xb, fd) elif xa==MidiType.CONTROL_CHANGE: return ControlChange(xb, fd) elif xa==MidiType.PROGRAM_CHANGE: return ProgramChange(xb, fd) elif xa==MidiType.CHANNEL_PRESSURE: return ChannelPressure(xb, fd) elif xa==MidiType.PITCH_WHEEL_CHANGE: return PitchWheelChange(xb, fd) elif first==MidiType.SYSTEM_EXCLUSIVE: return SystemExclusive(fd) elif first==MidiType.SONG_POSITION_POINTER: return SongPointerPosition(fd) elif first==MidiType.SONG_SELECT: return SongSelect(fd) elif first==MidiType.TUNE_REQUEST: return TuneRequest(fd) elif first==MidiType.TIMING_CLOCK: return TimingClock(fd) elif first==MidiType.START: return Start(fd) elif first==MidiType.CONTINUE: return Continue(fd) elif first==MidiType.STOP: return Stop(fd) elif first==MidiType.ACTIVE_SENSING: return ActiveSensing(fd) elif first==MidiType.META: return MidiMessage.parseMeta( fd) else: return MidiError(first) @staticmethod def parseMeta(fd): cmd=MidiMessage.byte(fd) if cmd==MidiType.META_SEQ: return SequenceNumber(fd) elif cmd==MidiType.META_TEXT: return TextEvent(fd) elif cmd==MidiType.META_COPYRIGHT: return Copyright(fd) elif cmd==MidiType.META_TRACK_NAME: return TrackName(fd) elif cmd==MidiType.META_INSTRUMENT_NAME: return InstrumentName(fd) elif cmd==MidiType.META_LYRICS: return TextLyrics(fd) elif cmd==MidiType.META_TEXT_MARKER: return TextMarker(fd) elif cmd==MidiType.META_CUE_POINT: return CuePoint(fd) elif cmd==MidiType.META_CHANNEL_PREFIX: return ChannelPrefix(fd) elif cmd==MidiType.META_END_OF_TRACK: return EndOfTrack(fd) elif cmd==MidiType.META_SET_TEMPO: return SetTempo(fd) elif cmd==MidiType.META_SMPTE_OFFSET: return SMPTEOffset(fd) elif cmd==MidiType.META_TIME_SIGNATURE: return TimeSignature(fd) elif cmd==MidiType.META_KEY_SIGNATURE: return KeySignature(fd) elif cmd==MidiType.META_SPECIFIC: return MetaSpecific(fd) else: return MidiError(cmd, fd.offset) """ Voices Messages """ class MidiVoiceMessage(MidiMessage): def __init__(self, typ, ch, fd=None): MidiMessage.__init__(self, typ) self.channel=ch+1 class NoteOn(MidiVoiceMessage): def __init__(self, ch, fd=None): MidiVoiceMessage.__init__(self, MidiType.NOTE_ON, ch) if fd: self.key=self.byte(fd) self.velocity=self.byte(fd) def bytes(self): return [MidiType.NOTE_ON | (self.channel-1), self.key, self.velocity] @staticmethod def new(chan, key, vel): x=NoteOn(chan+-1) x.key=key x.velocity=vel return x def transpose(self, n): self.key+=n def __str__(self): return "NOTEON("+str(self.key)+", "+ str(self.channel) +")" class NoteOff(MidiVoiceMessage): def __init__(self, ch, fd=None): MidiVoiceMessage.__init__(self, MidiType.NOTE_OFF, ch) if fd: self.key=self.byte(fd) self.velocity=self.byte(fd) def bytes(self): return [MidiType.NOTE_OFF | (self.channel-1), self.key, self.velocity] @staticmethod def new( chan, key, vel): x=NoteOff(chan-1) x.key=key x.velocity=vel return x def transpose(self, n): self.key+=n class KeyPressure(MidiVoiceMessage): def __init__(self, ch, fd=None): MidiVoiceMessage.__init__(self, MidiType.KEY_PRESSURE, ch) if fd: self.key=self.byte(fd) self.pressure=self.byte(fd) def bytes(self): return [MidiType.KEY_PRESSURE | (self.channel-1), self.key, self.value] def new(self, chan, key, pressure): x=KeyPressure(chan-1) x.key=key x.pressure=pressure return x class ControlChange(MidiVoiceMessage): def __init__(self, ch, fd=None): MidiVoiceMessage.__init__(self, MidiType.CONTROL_CHANGE, ch) if fd: self.key=self.byte(fd) self.value=self.byte(fd) def bytes(self): return [MidiType.CONTROL_CHANGE | (self.channel-1), self.key, self.value] def new(self, chan, key, value): x=ControlChange(chan-1) x.key=key x.value=value return x class ProgramChange(MidiVoiceMessage): def __init__(self, ch, fd=None): MidiVoiceMessage.__init__(self, MidiType.PROGRAM_CHANGE, ch) if fd: self.key=self.byte(fd) def bytes(self): return [MidiType.PROGRAM_CHANGE | (self.channel-1), self.key] def new(self, chan, key): x=ProgramChange(chan-1) x.key=key return x class ChannelPressure(MidiVoiceMessage): def __init__(self, ch, fd=None): MidiVoiceMessage.__init__(self, MidiType.CHANNEL_PRESSURE, ch) if fd: self.pressure=self.byte(fd) def bytes(self): return [MidiType.CHANNEL_PRESSURE | (self.channel-1), self.key, self.pressure] def new(self, chan, pressure): x=ChannelPressure(chan-1) x.pressure=pressure return x class PitchWheelChange(MidiVoiceMessage): def __init__(self, ch, fd=None): MidiVoiceMessage.__init__(self, MidiType.PITCH_WHEEL_CHANGE, ch) if fd: self.pressure=self.byte(fd) self.pressure+=(self.byte(fd)<<7) def bytes(self): return [MidiType.PITCH_WHEEL_CHANGE | (self.channel-1), self.pressure & 0x7f, (self.pressure & 0x3f8)>>7] def new(self, chan, pressure): x=PitchWheelChange(chan-1) x.pressure=pressure return x """ System Common Messages """ class SystemExclusive(MidiMessage): def __init__(self, fd=None): MidiMessage.__init__(self, MidiType.SYSTEM_EXCLUSIVE) if fd: self.data=[] while True: x=self.byte(fd) if x==MidiMessage.END_OF_EXCUSIVE: break else: self.data.append(x) def bytes(self): return [MidiType.SYSTEM_EXCLUSIVE]+self.data+[MidiType.END_OF_EXCUSIVE] def new(self, data): x=SystemExclusive(chan) x.data=data return x class SongPointerPosition(MidiMessage): def __init__(self, fd=None): MidiMessage.__init__(self, MidiType.SONG_POSITION_POINTER) if fd: self.beats=self.byte(fd) self.beats+=(self.byte(fd)<<7) def bytes(self): return [MidiType.SONG_POSITION_POINTER, self.beats & 0x7f, (self.beats & 0x3f8)>>7] def new(self, beats): x=SongPointerPosition(chan) x.beats=beats return x class SongSelect(MidiMessage): def __init__(self, fd=None): MidiMessage.__init__(self, MidiType.SONG_SELECT) if fd: self.song=fd.self.byte(fd) def bytes(self): return [MidiType.SONG_SELECT, self.song] def new(self, song): x=SongSelect(chan) x.song=song return x class TuneRequest(MidiMessage): def __init__(self, fd=None): MidiMessage.__init__(self, MidiType.TUNE_REQUEST) def bytes(self): return [MidiType.TUNE_REQUEST] def new(self): return TuneRequest() """ System RT Messages """ class TimingClock(MidiMessage): def __init__(self, fd=None): MidiMessage.__init__(self, MidiType.TimingClock) def bytes(self): return [MidiType.TIMING_CLOCK] def new(self): return TimingClock() class Start(MidiMessage): def __init__(self, fd=None): MidiMessage.__init__(self, MidiType.START) def bytes(self): return [MidiType.START] def new(self): return Start() class Continue(MidiMessage): def __init__(self, fd=None): MidiMessage.__init__(self, MidiType.CONTINUE) def bytes(self): return [MidiType.CONTINUE] def new(self): return Continue() class Stop(MidiMessage): def __init__(self, fd=None): MidiMessage.__init__(self, MidiType.STOP) def bytes(self): return [MidiType.STOP] def new(self): return Stop() class ActiveSensing(MidiMessage): def __init__(self, fd=None): MidiMessage.__init__(self, MidiType.ACTIVE_SENSING) def bytes(self): return [MidiType.ACTIVE_SENSING] def new(self): return ActiveSensing() """ Meta """ class Meta(MidiMessage): def __init__(self, cmd): MidiMessage.__init__(self, MidiType.META) self.command=cmd class EndOfTrack(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_END_OF_TRACK) if fd: self.byte(fd) def isEndTrack(self): return True def bytes(self): return [MidiType.META, self.command, 0] def new(self): return EndOfTrack() class SequenceNumber(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_SEQ) if fd: self.number=self.byte(fd) def bytes(self): return [MidiType.META, self.command, 0x2] def new(self): return SequenceNumber() class TextEvent(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_TEXT) if fd: self.text=fd.read(self.byte(fd)).decode("utf-8") def bytes(self): return [MidiType.META, self.command, len(self.text)]+[e.encode("hex") for e in self.text] def new(self, text): x=TextEvent() self.text=text return x class Copyright(Meta): def __init__(self,cmd, fd=None): Meta.__init__(self, MidiType.META_COPYRIGHT) if fd: self.text=fd.read(self.byte(fd)).decode("utf-8") def bytes(self): return [MidiType.META, self.command, len(self.text)]+[e.encode("hex") for e in self.text] def new(self, text): x=Copyright() self.text=text return x class TrackName(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_TRACK_NAME) if fd: self.text=fd.read(self.byte(fd)).decode("utf-8") def bytes(self): return [MidiType.META, self.command, len(self.text)]+[e.encode("hex") for e in self.text] def new(self, text): x=TrackName() self.text=text return x class InstrumentName(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_INSTRUMENT_NAME) if fd: self.text=fd.read(self.byte(fd)).decode("utf-8") def bytes(self): return [MidiType.META, self.command, len(self.text)]+[e.encode("hex") for e in self.text] def new(self, text): x=InstrumentName() self.text=text return x class TextLyrics(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_LYRICS) if fd: self.text=fd.read(self.byte(fd)).decode("utf-8") def bytes(self): return [MidiType.META, self.command, len(self.text)]+[e.encode("hex") for e in self.text] def new(self, text): x=TextLyrics() self.text=text return x class TextMarker(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_TEXT_MARKER) if fd: self.text=fd.read(self.byte(fd)).decode("utf-8") def bytes(self): return [MidiType.META, self.command, len(self.text)]+[e.encode("hex") for e in self.text] def new(self, text): x=TextMarker() self.text=text return x class CuePoint(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_CUE_POINT) if fd: self.text=fd.read(self.byte(fd)).decode("utf-8") def bytes(self): return [MidiType.META, self.command, len(self.text)]+[e.encode("hex") for e in self.text] def new(self, text): x=CuePoint() self.text=text return x class ChannelPrefix(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_CHANNEL_PREFIX) if fd: self.byte(fd) self.channelPrefix=self.byte(fd) def bytes(self): return [MidiType.META, self.command, 1, self.channelPrefix] def new(self, channelPrefix): x=ChannelPrefix() self.channelPrefix=channelPrefix return x class SetTempo(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_SET_TEMPO) if fd: self.byte(fd) self.tempo=self.byte(fd)<<16 self.tempo+=self.byte(fd)<<8 self.tempo+=self.byte(fd) def bytes(self): x=self.tempo return [MidiType.META, self.command, 3, (x&0xFF0000)>>16, (x&0xFF00)>>8, x&0xFF ] def new(self, tempo): x=SetTempo() self.tempo=tempo return x class SMPTEOffset(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_SMPTE_OFFSET) if fd: self.byte(fd) self.hour=self.byte(fd) self.minutes=self.byte(fd) self.seconds=self.byte(fd) self.frame=self.byte(fd) self.fraction=self.byte(fd) def bytes(self): return [MidiType.META, self.command, 5, self.hour, self.minutes, self.seconds, self.frame, self.fraction] def new(self, hh, mm, ss, fr, ff): x=SMPTEOffset() x.hour=hh x.minutes=mm x.seconds=ss x.frame=fr x.fraction=ff return x class TimeSignature(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_TIME_SIGNATURE) if fd: self.byte(fd) self.numerator=self.byte(fd) self.denominator=self.byte(fd) self.clic=self.byte(fd) self.notes=self.byte(fd) def bytes(self): return [MidiType.META, self.command, 4, self.numerator, self.denominator, self.clic, self.notes] def new(self, numerator, denominator, clic, notes): x=TimeSignature() x.numerator=numerator x.denominator=denominator x.clic=clic x.notes=notes return x class KeySignature(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_KEY_SIGNATURE) if fd: self.byte(fd) self.sf=self.byte(fd) self.mi=self.byte(fd) def bytes(self): return [MidiType.META, self.command, 2, self.sf, self.mi] def new(self, sf, mi): x=KeySignature() self.sf=sf self.mi=mi return x class MetaSpecific(Meta): def __init__(self, fd=None): Meta.__init__(self, MidiType.META_SPECIFIC) if fd: self.length=self.byte(fd) self.data=self.read(fd, self.length) def bytes(self): return [MidiType.META, self.command, self.length, self.data] def new(self, data): x=KeySignature() self.data=data self.length=len(data) return x """ Error """ class MidiError(MidiMessage): def __init__(self, first, offset): MidiMessage.__init__(self, MidiType.ERROR) raise MIDIParserException("First Midi byte ("+hex(first)+") is unknown at: "+hex(offset))