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

371 lines
11 KiB
Python
Raw Permalink Normal View History

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