371 lines
11 KiB
Python
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
|