jam-cloud/lambda/jamtrack-importer/shared/jmep/jparser.py

371 lines
11 KiB
Python

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