Add TJA parser (translated from JavaScript)
Source: https://github.com/WHMHammer/tja-tools/blob/master/src/js/parseTJA.js
This commit is contained in:
parent
8fa6295efa
commit
93dd1355f8
379
tja2fumen/parsetja.py
Normal file
379
tja2fumen/parsetja.py
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
# Original source: https://github.com/WHMHammer/tja-tools/blob/master/src/js/parseTJA.js
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
HEADER_GLOBAL = [
|
||||||
|
'TITLE',
|
||||||
|
'TITLEJA',
|
||||||
|
'SUBTITLE',
|
||||||
|
'SUBTITLEJA',
|
||||||
|
'BPM',
|
||||||
|
'WAVE',
|
||||||
|
'OFFSET',
|
||||||
|
'DEMOSTART',
|
||||||
|
'GENRE',
|
||||||
|
]
|
||||||
|
|
||||||
|
HEADER_COURSE = [
|
||||||
|
'COURSE',
|
||||||
|
'LEVEL',
|
||||||
|
'BALLOON',
|
||||||
|
'SCOREINIT',
|
||||||
|
'SCOREDIFF',
|
||||||
|
'TTRO'
|
||||||
|
'WBEAT',
|
||||||
|
]
|
||||||
|
|
||||||
|
COMMAND = [
|
||||||
|
'START',
|
||||||
|
'END',
|
||||||
|
'GOGOSTART',
|
||||||
|
'GOGOEND',
|
||||||
|
'MEASURE',
|
||||||
|
'SCROLL',
|
||||||
|
'BPMCHANGE',
|
||||||
|
'DELAY',
|
||||||
|
'BRANCHSTART',
|
||||||
|
'BRANCHEND',
|
||||||
|
'SECTION',
|
||||||
|
'N',
|
||||||
|
'E',
|
||||||
|
'M',
|
||||||
|
'LEVELHOLD',
|
||||||
|
'BMSCROLL',
|
||||||
|
'HBSCROLL',
|
||||||
|
'BARLINEOFF',
|
||||||
|
'BARLINEON',
|
||||||
|
'TTBREAK',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def parseLine(line):
|
||||||
|
match_comment = re.match(r"//.*", line)
|
||||||
|
match_header = re.match(r"^([A-Z]+):(.+)", line)
|
||||||
|
match_command = re.match(r"^#([A-Z]+)(?:\s+(.+))?", line)
|
||||||
|
match_data = re.match(r"^(([0-9]|A|B|C|F|G)*,?)$", line)
|
||||||
|
|
||||||
|
# comment
|
||||||
|
if match_comment:
|
||||||
|
return {
|
||||||
|
"type": 'comment',
|
||||||
|
"value": line
|
||||||
|
} # js: line = line.substr(0, match.index).strip()
|
||||||
|
|
||||||
|
# header
|
||||||
|
elif match_header:
|
||||||
|
nameUpper = match_header.group(1).upper()
|
||||||
|
value = match_header.group(2)
|
||||||
|
|
||||||
|
if nameUpper in HEADER_GLOBAL:
|
||||||
|
return {
|
||||||
|
"type": 'header',
|
||||||
|
"scope": 'global',
|
||||||
|
"name": nameUpper,
|
||||||
|
"value": value.strip(),
|
||||||
|
}
|
||||||
|
elif nameUpper in HEADER_COURSE:
|
||||||
|
return {
|
||||||
|
"type": 'header',
|
||||||
|
"scope": 'course',
|
||||||
|
"name": nameUpper,
|
||||||
|
"value": value.strip(),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
breakpoint()
|
||||||
|
|
||||||
|
# command
|
||||||
|
elif match_command:
|
||||||
|
nameUpper = match_command.group(1).upper()
|
||||||
|
value = match_command.group(2)
|
||||||
|
if not value:
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
if nameUpper in COMMAND:
|
||||||
|
return {
|
||||||
|
"type": 'command',
|
||||||
|
"name": nameUpper,
|
||||||
|
"value": value.strip(),
|
||||||
|
}
|
||||||
|
|
||||||
|
# data
|
||||||
|
elif match_data:
|
||||||
|
data = match_data.group(1)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"type": 'data',
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
|
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"type": 'unknown',
|
||||||
|
"value": line,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def getCourse(tjaHeaders, lines):
|
||||||
|
headers = {
|
||||||
|
"course": 'Oni',
|
||||||
|
"level": 0,
|
||||||
|
"balloon": [],
|
||||||
|
"scoreInit": 100,
|
||||||
|
"scoreDiff": 100,
|
||||||
|
"ttRowBeat": 16,
|
||||||
|
}
|
||||||
|
|
||||||
|
measures = []
|
||||||
|
measureDividend = 4
|
||||||
|
measureDivisor = 4
|
||||||
|
measureProperties = {}
|
||||||
|
measureData = ''
|
||||||
|
measureEvents = []
|
||||||
|
currentBranch = 'N'
|
||||||
|
targetBranch = 'N'
|
||||||
|
flagLevelhold = False
|
||||||
|
|
||||||
|
# Process lines
|
||||||
|
for line in lines:
|
||||||
|
if line["type"] == 'header':
|
||||||
|
if line["name"] == 'COURSE':
|
||||||
|
headers['course'] = line['value']
|
||||||
|
|
||||||
|
elif line["name"] == 'LEVEL':
|
||||||
|
headers['level'] = int(line['value'])
|
||||||
|
|
||||||
|
elif line["name"] == 'BALLOON':
|
||||||
|
if line['value']:
|
||||||
|
balloons = [int(v) for v in line['value'].split(",")]
|
||||||
|
else:
|
||||||
|
balloons = []
|
||||||
|
headers['balloon'] = balloons
|
||||||
|
|
||||||
|
elif line["name"] == 'SCOREINIT':
|
||||||
|
headers['scoreInit'] = int(line['value'])
|
||||||
|
|
||||||
|
elif line["name"] == 'SCOREDIFF':
|
||||||
|
headers['scoreDiff'] = int(line['value'])
|
||||||
|
|
||||||
|
elif line["name"] == 'TTROWBEAT':
|
||||||
|
headers['ttRowBeat'] = int(line['value'])
|
||||||
|
|
||||||
|
elif line["type"] == 'command':
|
||||||
|
if line["name"] == 'BRANCHSTART':
|
||||||
|
if flagLevelhold:
|
||||||
|
continue
|
||||||
|
values = line['value'].split(',')
|
||||||
|
if values[0] == 'r':
|
||||||
|
if len(values) >= 3:
|
||||||
|
targetBranch = 'M'
|
||||||
|
elif len(values) == 2:
|
||||||
|
targetBranch = 'E'
|
||||||
|
else:
|
||||||
|
targetBranch = 'N'
|
||||||
|
elif values[0] == 'p':
|
||||||
|
if len(values) >= 3 and float(values[2]) <= 100:
|
||||||
|
targetBranch = 'M'
|
||||||
|
elif len(values) >= 2 and float(values[1]) <= 100:
|
||||||
|
targetBranch = 'E'
|
||||||
|
else:
|
||||||
|
targetBranch = 'N'
|
||||||
|
|
||||||
|
elif line["name"] == 'BRANCHEND':
|
||||||
|
currentBranch = targetBranch
|
||||||
|
|
||||||
|
elif line["name"] == 'N':
|
||||||
|
currentBranch = 'N'
|
||||||
|
|
||||||
|
elif line["name"] == 'E':
|
||||||
|
currentBranch = 'E'
|
||||||
|
|
||||||
|
elif line["name"] == 'M':
|
||||||
|
currentBranch = 'M'
|
||||||
|
|
||||||
|
elif line["name"] == 'START':
|
||||||
|
currentBranch = 'N'
|
||||||
|
targetBranch = 'N'
|
||||||
|
flagLevelhold = False
|
||||||
|
|
||||||
|
elif line["name"] == 'END':
|
||||||
|
currentBranch = 'N'
|
||||||
|
targetBranch = 'N'
|
||||||
|
flagLevelhold = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
if currentBranch != targetBranch:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line['name'] == 'MEASURE':
|
||||||
|
matchMeasure = re.match(r"(\d+)/(\d+)", line['value'])
|
||||||
|
if not matchMeasure:
|
||||||
|
continue
|
||||||
|
measureDividend = int(matchMeasure.group(1))
|
||||||
|
measureDivisor = int(matchMeasure.group(2))
|
||||||
|
|
||||||
|
elif line['name'] == 'GOGOSTART':
|
||||||
|
measureEvents.append({
|
||||||
|
"name": 'gogoStart',
|
||||||
|
"position": len(measureData),
|
||||||
|
})
|
||||||
|
|
||||||
|
elif line['name'] == 'GOGOEND':
|
||||||
|
measureEvents.append({
|
||||||
|
"name": 'gogoEnd',
|
||||||
|
"position": len(measureData),
|
||||||
|
})
|
||||||
|
|
||||||
|
elif line['name'] == 'SCROLL':
|
||||||
|
measureEvents.append({
|
||||||
|
"name": 'scroll',
|
||||||
|
"position": len(measureData),
|
||||||
|
"value": float(line['value']),
|
||||||
|
})
|
||||||
|
|
||||||
|
elif line['name'] == 'BPMCHANGE':
|
||||||
|
measureEvents.append({
|
||||||
|
"name": 'bpm',
|
||||||
|
"position": len(measureData),
|
||||||
|
"value": float(line['value']),
|
||||||
|
})
|
||||||
|
|
||||||
|
elif line['name'] == 'TTBREAK':
|
||||||
|
measureProperties['ttBreak'] = True
|
||||||
|
|
||||||
|
elif line['name'] == 'LEVELHOLD':
|
||||||
|
flagLevelhold = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(line['name']) # Unknown: BARLINEOFF, BARLINEON
|
||||||
|
|
||||||
|
elif line['type'] == 'data' and currentBranch is targetBranch:
|
||||||
|
data = line['data']
|
||||||
|
if data.endswith(','):
|
||||||
|
measureData += data[0:-1]
|
||||||
|
measure = {
|
||||||
|
"length": [measureDividend, measureDivisor],
|
||||||
|
"properties": measureProperties,
|
||||||
|
"data": measureData,
|
||||||
|
"events": measureEvents,
|
||||||
|
}
|
||||||
|
measures.append(measure)
|
||||||
|
measureData = ''
|
||||||
|
measureEvents = []
|
||||||
|
measureProperties = {}
|
||||||
|
else:
|
||||||
|
measureData += data
|
||||||
|
|
||||||
|
if len(measures):
|
||||||
|
# Make first BPM event
|
||||||
|
firstBPMEventFound = False
|
||||||
|
# Search for BPM event in the first measure
|
||||||
|
for i in range(len(measures[0]['events'])):
|
||||||
|
evt = measures[0]['events'][i]
|
||||||
|
if evt.name == 'bpm' and evt.position == 0:
|
||||||
|
firstBPMEventFound = True
|
||||||
|
if not firstBPMEventFound:
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
measures[0]['events'].insert(0, {
|
||||||
|
"name": 'bpm',
|
||||||
|
"position": 0,
|
||||||
|
"value": tjaHeaders['bpm'],
|
||||||
|
})
|
||||||
|
|
||||||
|
# Helper values
|
||||||
|
course = 0
|
||||||
|
courseValue = headers['course'].lower()
|
||||||
|
|
||||||
|
if courseValue in ['easy', '0']:
|
||||||
|
course = 0
|
||||||
|
elif courseValue in ['normal', '1']:
|
||||||
|
course = 1
|
||||||
|
elif courseValue in ['hard', '2']:
|
||||||
|
course = 2
|
||||||
|
elif courseValue in ['oni', '3']:
|
||||||
|
course = 3
|
||||||
|
elif courseValue in ['ura', 'edit', '4']:
|
||||||
|
course = 4
|
||||||
|
|
||||||
|
if measureData:
|
||||||
|
measures.append({
|
||||||
|
"length": [measureDividend, measureDivisor],
|
||||||
|
"properties": measureProperties,
|
||||||
|
"data": measureData,
|
||||||
|
"events": measureEvents,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
for event in measureEvents:
|
||||||
|
event['position'] = len(measures[len(measures) - 1]['data'])
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
measures[len(measures) - 1]['events'].append(event)
|
||||||
|
|
||||||
|
# Output
|
||||||
|
print(measures[len(measures) - 1])
|
||||||
|
return course, headers, measures
|
||||||
|
|
||||||
|
|
||||||
|
def parseTJA(tja):
|
||||||
|
# Split by lines
|
||||||
|
lines = tja.read().splitlines()
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"title": '',
|
||||||
|
"subtitle": '',
|
||||||
|
"bpm": 120,
|
||||||
|
"wave": '',
|
||||||
|
"offset": 0,
|
||||||
|
"demoStart": 0,
|
||||||
|
"genre": '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Line by line
|
||||||
|
courses = {}
|
||||||
|
courseLines = []
|
||||||
|
for line in lines:
|
||||||
|
if line == '':
|
||||||
|
continue
|
||||||
|
|
||||||
|
parsed = parseLine(line)
|
||||||
|
if parsed['type'] == 'header' and parsed['scope'] == 'global':
|
||||||
|
if parsed['name'] == 'TITLE':
|
||||||
|
headers['title'] = parsed['value']
|
||||||
|
elif parsed['name'] == 'TITLEJA':
|
||||||
|
headers['titleja'] = parsed['value']
|
||||||
|
elif parsed['name'] == 'SUBTITLE':
|
||||||
|
headers['subtitle'] = parsed['value']
|
||||||
|
elif parsed['name'] == 'SUBTITLEJA':
|
||||||
|
headers['subtitleja'] = parsed['value']
|
||||||
|
elif parsed['name'] == 'BPM':
|
||||||
|
headers['bpm'] = float(parsed['value'])
|
||||||
|
elif parsed['name'] == 'WAVE':
|
||||||
|
headers['wave'] = parsed['value']
|
||||||
|
elif parsed['name'] == 'OFFSET':
|
||||||
|
headers['offset'] = float(parsed['value'])
|
||||||
|
elif parsed['name'] == 'DEMOSTART':
|
||||||
|
headers['demoStart'] = float(parsed['value'])
|
||||||
|
elif parsed['name'] == 'GENRE':
|
||||||
|
headers['genre'] = parsed['value']
|
||||||
|
|
||||||
|
elif parsed['type'] == 'header' and parsed['scope'] == 'course':
|
||||||
|
if parsed['name'] == 'COURSE':
|
||||||
|
if courseLines:
|
||||||
|
course = getCourse(headers, courseLines)
|
||||||
|
courses[course[1]['course']] = course
|
||||||
|
else:
|
||||||
|
pass # This is the first course, so we haven't parsed its lines ye
|
||||||
|
|
||||||
|
courseLines.append(parsed)
|
||||||
|
|
||||||
|
elif parsed['type'] == 'command':
|
||||||
|
courseLines.append(parsed)
|
||||||
|
|
||||||
|
elif parsed['type'] == 'data':
|
||||||
|
courseLines.append(parsed)
|
||||||
|
|
||||||
|
# Parse the lines for the last course (since there are no more course headers left)
|
||||||
|
if courseLines:
|
||||||
|
course = getCourse(headers, courseLines)
|
||||||
|
courses[course[1]['course']] = course
|
||||||
|
|
||||||
|
# Return
|
||||||
|
return headers, courses
|
Loading…
Reference in New Issue
Block a user