import json from pprint import pprint import sys import os import keywords import metronome import math from timestamp import formatTimeStamp JMEP_START_TIMESTAMP = (-100000) DEBUG_ENABLE = 0 def is_array(var): return isinstance(var, (list, tuple)) class JmepParser: outFile='' inFile='' ts = 0.0 lineNumber = 0 header = {} header['version'] = 1 header['copyright'] = "JamKazam 2015" metroList = [] preludeList = [] playList=[] syncList = [] def __init__(self, fileName, ts=JMEP_START_TIMESTAMP, outKeyFile='', lineNumber=0): self.inFile = fileName self.outFile = outKeyFile self.ts = ts self.lineNumber = lineNumber #self.processFile() def is_number(self,s): try: float(s) return True except ValueError: return False def ValidTsFormat(self,t): sgn = 0 if t[0] == '+' or t[0] == '-': #is forward relative to current time if t[0] == '+': sgn = 1 else: sgn = -1 t = t[1:] if self.is_number(t): if sgn: tVal = float(t) * sgn + self.ts return (True, tVal) else: tVal = float(t) return (True, tVal) #check if h:m:s:ms tt = t.split(':') if len(tt) != 4: print("Unknown timestamp format at ",self.inFile,":",self.lineNumber) return (False,0) #covert to absolute number of seconds tv = float(tt[0])*3600 + float(tt[1])*60 + float(tt[2]) + float(tt[3])/1000.0 if sgn: tVal = tv * sgn + self.ts return (True, tVal) else: tVal = tv return (True, tVal) def extractNumval(self,nameVal,lineNumer): try: val = float(nameVal[1]) return (True,val) except: print("Invalid name value format '",nameVal,"' at ",self.inFile,":",lineNumer) return (False,0) def extraStringVal(self,nameVal,validator,lineNumber): if not is_array(nameVal): print("Invalid value in format '",nameVal,"' at ",self.inFile,":",lineNumber) return (False,'') str = nameVal[1].lower().strip() if validator: if str not in validator: print("Invalid value in format '",nameVal,"' at ",self.inFile,":",lineNumber) return (False,'') return (True,str) def processTimestamp(self,kw): mylist = kw.split('@') t = mylist[1] (val,t) = self.ValidTsFormat(t) return (val,t) def metronome(self, val, bStart, arg, lineNumber): (val,t) = self.processTimestamp(val) if not val: return False ticks = 0 bpm = 0 duration = 0 vol = 100 meter = 1 name = 'default' pmode='stream' play='mono' #create tuples of (name,values) valid_names = ['default','sine', 'click','kick', 'beep','snare'] valid_play = ['mono','left','right'] valid_pmode = ['stream','distributed'] args = arg.split(',') for nameVal in args: if '=' not in nameVal: print("Invalid name value format '",nameVal,"' at ",self.inFile,":",lineNumber) return False nv = nameVal.split("=") n = nv[0].lower().strip() b = True if n == 'bpm': (b,bpm) = self.extractNumval(nv,lineNumber) elif n == 'ticks': (b,ticks) = self.extractNumval(nv,lineNumber) elif n == 'vol': (b,vol) = self.extractNumval(nv,lineNumber) elif n == 'len': (b,duration) = self.extractNumval(nv,lineNumber) elif n == 'pmode': (b,pmode) = self.extraStringVal(nv,valid_pmode,lineNumber) elif n == 'name': (b,name) = self.extraStringVal(nv,valid_names,lineNumber) elif n == 'play': (b,play) = self.extraStringVal(nv,valid_play,lineNumber) else: print("Invalid name value format '",nameVal,"' at ",self.inFile,":",lineNumber) return False #check if argument was not a number if not b: return False if duration and ticks: print("Cannot specify both len and ticks '",self.inFile,":",lineNumber) return False if not bpm: print("BPM value must be specified '",self.inFile,":",lineNumber) return False if ticks: duration = ticks*60/bpm tStart = 0 tStop = 0 if bStart: tStart = t tStop = t + duration else: tStart = t - duration tStop = t mn = metronome.Metronome(ticks, bpm, tStart, tStop, vol, pmode, name,play, meter) #check for time overlap for om in self.metroList: if mn.doesTimeOverlap(om): print("Metronome time range overlap '",nameVal,"' at ",self.inFile,":",lineNumber) return False self.metroList.append(mn) #newlist = sorted(self.metroList, key=lambda x: x.startTs, reverse=True) return True def prelude(self, val, arg, lineNumber): (val,t) = self.processTimestamp(val) if not val: return False dt = {} t = abs(t)* -1.0 dt['ts']= formatTimeStamp(t) dt['duration'] = t dt['count'] = t self.preludeList = [] self.preludeList.append(dt) return True def play(self, val, arg, lineNumber): (val,t) = self.processTimestamp(val) if not val: return False if t: print("Play time must be 0 '",val,"' at ",self.inFile,":",lineNumber) return False pd = {} pd['ts']=formatTimeStamp(0) pdTrack = ['All'] pd['tracks']=pdTrack self.playList = pd return True def syncTs(self, val, arg, lineNumber): (val,t) = self.processTimestamp(val) if not val: return False pd = {} pd['ts'] = formatTimeStamp(t) self.syncList.append(pd) return True def includeFile(self,val, arg, lineNumber): (val,t) = self.processTimestamp(val) if not val: return False p = JmepParser(arg,t,lineNumber) if not p.processFile(): print("Error processing include file'",val,"' at ",self.inFile,":",lineNumber) #TODO #merge the objects return def setTimeStamp(self, val,arg, lineNumber): (val,t) = self.processTimestamp(val) if not val: return False self.ts = t return True def author(self, val, arg, lineNumber): args = arg.split(',') for nameVal in args: if '=' not in nameVal: print("Invalid name value format '",nameVal,"' at ",self.inFile,":",lineNumber) return False nv = nameVal.split("=") n = nv[0].lower().strip() if n == 'email': self.header['email'] = nv[1].strip() elif n == 'name': self.header['creator'] = nv[1].strip() else: print("Unknown name value format '",nameVal,"' at ",self.inFile,":",lineNumber) return True def generateJson(self): if not self.playList: #load default play all pd = {} pd['ts']=formatTimeStamp(0) pdTrack = ['All'] pd['tracks']=pdTrack self.playList = pd data = {} eventData = {} data['header']=self.header eventList = [] if self.metroList: newlist = sorted(self.metroList, key=lambda x: x.startTs, reverse=False) md = [] for x in newlist: y = x.generateStart() md.append(y) y = x.generateStop() md.append(y) mt={'metronome': md} eventList.append(mt) #eventData['metronome'] = md #print md if self.preludeList: mt={'count_down': self.preludeList} eventList.append(mt) #eventData['count_down'] = self.preludeList if self.playList: mt={'track_play': [self.playList]} eventList.append(mt) #eventData['track_play']= self.playList if self.syncList: mt={'sync': self.syncList} eventList.append(mt) #eventData['sync'] = self.syncList #make events into list #l = [] #l.append(eventData) data['Events'] = eventList jdata = json.dumps(data) pprint(jdata) fo = open(self.outFile, 'w') fo.write(jdata) fo.close() def processLine(self, str, lineNum): # remove comment part from string if '#' in str: mylist = str.split('#') str = mylist[0].strip(); #split string into name value pairs str.strip() #print str if not str: #empty string return True #pick of the first word - this is the key keyword = str.partition(' ')[0] #print 'keyword=',keyword args = str.replace(keyword,'') keyword = keyword.lower() b = False if keywords.METRO_START in keyword: b = self.metronome(keyword,True,args,lineNum) elif keywords.METRO_FIN in keyword: b= self.metronome(keyword,False,args,lineNum) elif keywords.PRELUDE in keyword: b = self.prelude(keyword,args,lineNum) elif keywords.PLAY_FILE in keyword: return self.play(keyword,args,lineNum) elif keywords.EXPLICIT_TIMESTAMP in keyword: b =self.setTimeStamp(keyword,args,lineNum) elif keywords.AUTHOR in keyword: b = self.author(keyword,args,lineNum) elif keywords.EXPLICIT_SYNC in keyword: b = self.syncTs(keyword,args,lineNum) elif keywords.INCLUDE_FILE in keyword: b = self.includeFile(keyword,args,lineNum) else: print("Unknown keyword '",keyword,"' at ",self.inFile,":",lineNum) return False return b def processFile(self): content = [] with open(self.inFile ) as f: content = f.readlines() content = [x.strip('\n') for x in content] #now remove comments portion from lines linenum = 1 for x in content: #print x self.lineNumber = linenum if DEBUG_ENABLE: print(linenum, x) if not self.processLine(x,linenum): return False linenum = linenum + 1 return True