1
0
mirror of synced 2024-11-28 06:50:49 +01:00

Add TJA parser (translated from JavaScript)

Source: https://github.com/WHMHammer/tja-tools/blob/master/src/js/parseTJA.js
This commit is contained in:
Viv 2023-06-02 16:33:43 -04:00
parent 8fa6295efa
commit 93dd1355f8

379
tja2fumen/parsetja.py Normal file
View 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