1
0
mirror of synced 2024-11-15 01:47:34 +01:00

Add remaining support for branches (#BRANCHSTART r) (#21)

This PR adds the remaining changes needed to support branching TJA
songs.

- Properly handling `r` (drumroll) based branching conditions
- Handling commands that come after a measure end but _before_ the first
`#BRANCHSTART` command. (This was not previously covered by
`hol6po.tja`.)

As well, it adds a bit of cleanup/refactoring to the changes from the
previous branch PR (#20).

To test these changes, another new branching TJA-fumen pair is added to
the test suite.
This commit is contained in:
Viv 2023-07-02 00:00:36 -04:00 committed by GitHub
parent 4e613d7237
commit d9b1476f4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 658 additions and 82 deletions

View File

@ -137,19 +137,17 @@ def convertTJAToFumen(tja):
# Check to see if the measure contains a branching condition
if measureTJA['branchStart']:
measureFumen['branchStart'] = measureTJA['branchStart']
if measureFumen['branchStart']:
if measureFumen['branchStart'][0] == 'p':
if measureTJA['branchStart'][0] == 'p':
if currentBranch == 'normal':
idx_b1, idx_b2 = 0, 1
elif currentBranch == 'advanced':
idx_b1, idx_b2 = 2, 3
elif currentBranch == 'master':
idx_b1, idx_b2 = 4, 5
measureFumen['branchInfo'][idx_b1] = int(total_notes_branch * measureFumen['branchStart'][1] * 20)
measureFumen['branchInfo'][idx_b2] = int(total_notes_branch * measureFumen['branchStart'][2] * 20)
measureFumen['branchInfo'][idx_b1] = int(total_notes_branch * measureTJA['branchStart'][1] * 20)
measureFumen['branchInfo'][idx_b2] = int(total_notes_branch * measureTJA['branchStart'][2] * 20)
elif measureTJA['branchStart'][0] == 'r':
pass
measureFumen['branchInfo'] = measureTJA['branchStart'][1:] * 3
total_notes_branch = 0
total_notes_branch += note_counter_branch

View File

@ -1,5 +1,6 @@
import os
import re
from copy import deepcopy
from tja2fumen.utils import readStruct, getBool, shortHex
from tja2fumen.constants import NORMALIZE_COURSE, TJA_NOTE_TYPES, branchNames, noteTypes
@ -89,98 +90,87 @@ def getCourseData(lines):
def parseCourseMeasures(lines):
# Check if the course has branches or not
hasBranches = True if [l for l in lines if l['name'] == 'BRANCHSTART'] else False
if hasBranches:
currentBranch = 'all'
targetBranch = 'all'
else:
currentBranch = 'normal'
targetBranch = 'normal'
currentBranch = 'all' if hasBranches else 'normal'
flagLevelhold = False
# Process course lines
branches = {'normal': [], 'advanced': [], 'master': []}
measureNotes = ''
measureEvents = []
idx_m = 0
idx_m_branchstart = 0
emptyMeasure = {'data': '', 'events': []}
branches = {'normal': [deepcopy(emptyMeasure)], 'advanced': [deepcopy(emptyMeasure)], 'master': [deepcopy(emptyMeasure)]}
for line in lines:
# 1. Parse measure notes
if line['name'] == 'NOTES':
notes = line['value']
# If measure has ended, then append the measure and start anew
# If measure has ended, then add notes to the current measure, then start a new one by incrementing idx_m
if notes.endswith(','):
measureNotes += notes[0:-1]
measureCurrent = {
"data": measureNotes,
"events": measureEvents,
}
if currentBranch == 'all':
for branch in branches.keys():
branches[branch].append(measureCurrent)
else:
branches[currentBranch].append(measureCurrent)
measureNotes = ''
measureEvents = []
# Otherwise, keep tracking measureNotes
for branch in branches.keys() if currentBranch == 'all' else [currentBranch]:
branches[branch][idx_m]['data'] += notes[0:-1]
branches[branch].append(deepcopy(emptyMeasure))
idx_m += 1
# Otherwise, keep adding notes to the current measure ('idx_m')
else:
measureNotes += notes
for branch in branches.keys() if currentBranch == 'all' else [currentBranch]:
branches[branch][idx_m]['data'] += notes
# 2. Parse commands
else:
# Measure commands
# 2. Parse measure commands that produce an "event"
elif line['name'] in ['GOGOSTART', 'GOGOEND', 'BARLINEON', 'BARLINEOFF',
'SCROLL', 'BPMCHANGE', 'MEASURE', 'BRANCHSTART']:
# Get position of the event
for branch in branches.keys() if currentBranch == 'all' else [currentBranch]:
pos = len(branches[branch][idx_m]['data'])
# Parse event type
if line['name'] == 'GOGOSTART':
measureEvents.append({"name": 'gogo', "position": len(measureNotes), "value": '1'})
currentEvent = {"name": 'gogo', "position": pos, "value": '1'}
elif line['name'] == 'GOGOEND':
measureEvents.append({"name": 'gogo', "position": len(measureNotes), "value": '0'})
currentEvent = {"name": 'gogo', "position": pos, "value": '0'}
elif line['name'] == 'BARLINEON':
measureEvents.append({"name": 'barline', "position": len(measureNotes), "value": '1'})
currentEvent = {"name": 'barline', "position": pos, "value": '1'}
elif line['name'] == 'BARLINEOFF':
measureEvents.append({"name": 'barline', "position": len(measureNotes), "value": '0'})
currentEvent = {"name": 'barline', "position": pos, "value": '0'}
elif line['name'] == 'SCROLL':
measureEvents.append({"name": 'scroll', "position": len(measureNotes), "value": float(line['value'])})
currentEvent = {"name": 'scroll', "position": pos, "value": float(line['value'])}
elif line['name'] == 'BPMCHANGE':
measureEvents.append({"name": 'bpm', "position": len(measureNotes), "value": float(line['value'])})
currentEvent = {"name": 'bpm', "position": pos, "value": float(line['value'])}
elif line['name'] == 'MEASURE':
measureEvents.append({"name": 'measure', "position": len(measureNotes), "value": line['value']})
currentEvent = {"name": 'measure', "position": pos, "value": line['value']}
elif line["name"] == 'BRANCHSTART':
if flagLevelhold:
continue
currentBranch = 'all' # Ensure that the #BRANCHSTART command is present for all branches
values = line['value'].split(',')
if values[0] == 'r': # r = drumRoll
values[1] = int(values[1]) # # of drumrolls
values[2] = int(values[2]) # # of drumrolls
elif values[0] == 'p': # p = Percentage
values[1] = float(values[1]) / 100 # %
values[2] = float(values[2]) / 100 # %
currentEvent = {"name": 'branchStart', "position": pos, "value": values}
idx_m_branchstart = idx_m # Preserve the index of the BRANCHSTART command to re-use for each branch
# Branch commands
elif line["name"] == 'START' or line['name'] == 'END':
if hasBranches:
currentBranch = 'all'
targetBranch = 'all'
else:
currentBranch = 'normal'
targetBranch = 'normal'
# Append event to the current measure's events
for branch in branches.keys() if currentBranch == 'all' else [currentBranch]:
branches[branch][idx_m]['events'].append(currentEvent)
# 3. Parse commands that don't create an event (e.g. simply changing the current branch)
else:
if line["name"] == 'START' or line['name'] == 'END':
currentBranch = 'all' if hasBranches else 'normal'
flagLevelhold = False
elif line['name'] == 'LEVELHOLD':
flagLevelhold = True
elif line["name"] == 'N':
currentBranch = 'normal'
idx_m = idx_m_branchstart
elif line["name"] == 'E':
currentBranch = 'advanced'
idx_m = idx_m_branchstart
elif line["name"] == 'M':
currentBranch = 'master'
idx_m = idx_m_branchstart
elif line["name"] == 'BRANCHEND':
currentBranch = targetBranch
elif line["name"] == 'BRANCHSTART':
if flagLevelhold:
continue
values = line['value'].split(',')
if values[0] == 'r':
if len(values) >= 3:
targetBranch = 'master'
elif len(values) == 2:
targetBranch = 'advanced'
else:
targetBranch = 'normal'
elif values[0] == 'p': # p = percentage
values[1] = float(values[1]) / 100 # %
values[2] = float(values[2]) / 100 # %
measureEvents.append({"name": 'branchStart', "position": len(measureNotes), "value": values})
if len(values) >= 3 and float(values[2]) <= 100:
targetBranch = 'master'
elif len(values) >= 2 and float(values[1]) <= 100:
targetBranch = 'advanced'
else:
targetBranch = 'normal'
currentBranch = 'all'
# Ignored commands
elif line['name'] == 'LYRIC':
@ -190,23 +180,16 @@ def parseCourseMeasures(lines):
# Not implemented commands
elif line['name'] == 'SECTION':
pass # TODO: Implement
pass # This seems to be inconsequential, but I'm not 100% sure. Need to test more branching fumens.
elif line['name'] == 'DELAY':
raise NotImplementedError
else:
raise NotImplementedError
# If there is measure data (i.e. the file doesn't end on a "measure end" symbol ','), append whatever is left
if measureNotes:
branches[currentBranch].append({
"data": measureNotes,
"events": measureEvents,
})
# Otherwise, if the file ends on a measure event (e.g. #GOGOEND), append any remaining events
elif measureEvents:
for event in measureEvents:
event['position'] = len(branches[len(branches) - 1]['data'])
branches[currentBranch][len(branches[currentBranch]) - 1]['events'].append(event)
# Delete the last measure in the branch if no notes or events were added to it (due to preallocating empty measures)
for branch in branches.values():
if not branch[-1]['data'] and not branch[-1]['events']:
del branch[-1]
# Merge measure data and measure events in chronological order
for branchName, branch in branches.items():

594
testing/data/butou5.tja Normal file
View File

@ -0,0 +1,594 @@
BPM:148
OFFSET:-2.245
COURSE:Oni
LEVEL:8
BALLOON:
SCOREINIT:410
SCOREDIFF:100
#START
#MEASURE 5/4
500008000000000000000000000000000000000000000000000000000000,
#MEASURE 4/4
#BRANCHSTART r,1,2
#N
3002202030022020,
3002202020000000,
1010101010102220,
1010222020000000,
12221222,
1020220220000000,
1010222010102220,
2202202020000000,
1011101010111010,
1002202010111020,
1002202020111010,
1002202022202000,
1011101010111010,
1002202010111020,
1022002200202000,
1020222010202220,
1011101010111010,
1002202010111020,
2220202020002000,
1002202010022020,
1011101010111010,
2202202010111020,
1010222010102220,
1020220220202000,
1010111110222000,
1010111120222000,
1010111220022000,
1022202012202000,
1010111220022000,
1010112210222000,
1010122110222000,
1000000011111010,
#GOGOSTART
1011101010111010,
1002202010111020,
1002202010202020,
1020202022202000,
1011101010111010,
1002202010111020,
1002102020021022,
2020202020202022,
1011101010111010,
1022202010111020,
1022202010222020,
1020202020020020,
1011101010111010,
1022202010111020,
1002202012112011,
2020220220001000,
#GOGOEND
12212222,
2020220220000000,
1010101010102220,
1020222010202000,
1002202010022020,
1010222020101000,
11111111,
33333000,
1022202220101022,
1022202220101022,
1022202220102220,
1110222011101000,
1022202011122020,
1010222011101000,
1120221011202210,
1122112211221122,
1002,
#GOGOSTART
33,
3022202020000000,
1012202010122020,
1012202022202000,
33,
3022202010221010,
1012202010202000,
1022102210222220,
1011101010111010,
1022202010112020,
1022202010222020,
1022202020020020,
1011101010111010,
1022202010211010,
1111222211112222,
1122112030303000,
#E
3002202030022020,
3002202020000000,
1011101020102220,
1010222020000000,
32223222,
3020220220000000,
1010222010102220,
1201202020000000,
1022202010201122,
1002202010221020,
1002202012102000,
1002202022102000,
1022202010202211,
1022202010221020,
1022002210202000,
1002202010022020,
1022202010201122,
1002202010221020,
1221202020011010,
1002202022122020,
1022202010202120,
1102202010221020,
1010222010102220,
1020220220201000,
3030000000212000,
3030000022212000,
3030000020022000,
1002202212102000,
3030000020022000,
3030000022202000,
3030000022212000,
1111222210022020,
#GOGOSTART
1022202010201122,
1002202010221020,
1011202010112020,
1011202030303000,
1022202010201122,
1022202010221020,
3003003030111000,
34343434,
1022202010201122,
1002202010221020,
1011202010112020,
1011202022120020,
1022202010201122,
1022202010221020,
1012102102102000,
2020110110003000,
#GOGOEND
11111111,
1010110110000000,
1010101010102220,
1010222020101000,
1002202010022020,
1010222022101000,
32223222,
32223000,
2021202122101000,
1012122122102000,
1012101222102220,
1221221122102000,
2011201122102000,
1011221122102000,
1221221012212210,
2211221122112211,
2004,
#GOGOSTART
1022202010201122,
1002202010221020,
1002202020111000,
1002202011102000,
1022202010201122,
1002202010221020,
1002202011212000,
1022102210221111,
1022202010201122,
1022202010221020,
1011202010112020,
1011202022120020,
1022202010201122,
1022202010221020,
1221221112212211,
1221221110404000,
#M
1002202010022020,
1002202010000000,
12221212,
1020122010000000,
12121212,
1020120210000000,
1010222010102220,
1201203040000000,
1011202020111020,
1011202020111010,
1011202020111010,
1022102210221010,
1011202020111020,
1011202010221020,
1011202010112020,
1011201120112020,
1112202020111020,
1112202020111010,
1112202020111010,
1022102210221010,
1112202020111020,
1112202010221020,
1012221010122210,
1012221210101000,
1010112210000000,
1010221120000000,
2020112210000000,
1002102012201000,
3030112210222000,
3030221120222000,
4040112210222000,
3000000011111000,
#GOGOSTART
1011202020111020,
1011202020111010,
2212101022121010,
2212101010101000,
1011202020111020,
1011202010221020,
1021221010222020,
2012201220122010,
1112202020111020,
1112202020111010,
2210101022101010,
2210101012012010,
1112202020111020,
1112202010221020,
1021221021221020,
1012112210201000,
#GOGOEND
12212222,
2020220210000002,
1020201020202220,
1020222010202000,
1002202010022020,
1010222022101000,
12212122,
12122000,
1011201122101000,
1011221122102000,
1022102211202010,
2211221120101000,
2011201122202000,
2011212122202000,
2222111122221111,
2211221122112211,
202020202020202220400000,
#GOGOSTART
33,
3000000000112020,
1112202020111010,
1022102210221000,
33,
3000000000221020,
1112202010122020,
2010121012101210,
1112202020111020,
1112202020111010,
2210201022102010,
2210101012012010,
1112202020111020,
1112202010221020,
1111222211112222,
1111222030303000,
#BRANCHEND
#GOGOEND
#END
COURSE:Hard
LEVEL:5
BALLOON:14,16
SCOREINIT:520
SCOREDIFF:137
#START
#MEASURE 5/4
,
#MEASURE 4/4
1011100010111000,
10111000,
7,
00000800,
11101110,
1011101010000000,
7,
00000800,
1011100010001010,
1000000010111010,
1,
2022200020222000,
1011100010001010,
1000000010111010,
1,
1011100020222000,
1011100010001010,
1000000010111010,
1,
2022200020222000,
1011100010001010,
1000000010111010,
1,
1011100020222000,
1010111010000000,
1010111010000000,
1010222020000000,
11,
1010111010000000,
1010111010000000,
1010222020000000,
60000800,
#GOGOSTART
1011101010001010,
1000000010111010,
1,
2022200020222000,
1011101010001010,
1000000010111010,
1,
1011100020222000,
1011101010001010,
1000000010111010,
1,
2022200020222000,
1011101010001010,
1000000010111010,
1,
1011100020222000,
#GOGOEND
11022220,
11022220,
11022220,
11202220,
12201220,
11202000,
1111,
11111000,
2022202020002000,
2022202020002000,
22202220,
22222220,
2000200022202000,
2000200022202000,
22202220,
500000000000000000000000000000000000000000000008,
0,
#GOGOSTART
30003011,
3000000010111010,
1,
2022200020222000,
30003011,
3000000010111010,
1,
1011100020222000,
1011101010001010,
1000000010111010,
1,
2022200020222000,
1011101010001010,
1000000010111010,
1110101011101010,
1110101030303000,
#GOGOEND
,
,
#END
COURSE:Normal
LEVEL:4
BALLOON:10,12
SCOREINIT:720
SCOREDIFF:220
#START
#MEASURE 5/4
,
#MEASURE 4/4
11,
1110,
7,
00000800,
11,
1110,
7,
00000800,
10001011,
10001011,
1,
2220,
10001011,
10001011,
1,
2220,
10001011,
10001011,
1,
2220,
10001011,
10001011,
1,
22,
10111000,
10111000,
20222000,
11,
10111000,
10111000,
20222000,
60000800,
#GOGOSTART
10001011,
10001011,
1,
2220,
10001011,
10001011,
1,
2220,
10001011,
10001011,
1,
2220,
10001011,
10001011,
1,
22,
#GOGOEND
11000000,
11000000,
11000000,
11000000,
11,
11000000,
1111,
11101000,
2220,
2220,
2222,
22202000,
2220,
2220,
22202220,
500000000000000000000000000000000000000000000008,
,
#GOGOSTART
33,
3,
1212,
2220,
33,
3,
1212,
2220,
10001011,
1,
1212,
2220,
10001011,
11,
11101110,
11103030,
#GOGOEND
,
,
#END
COURSE:Easy
LEVEL:3
BALLOON:8,10
SCOREINIT:670
SCOREDIFF:255
#START
#MEASURE 5/4
,
#MEASURE 4/4
11,
1,
7,
00000800,
11,
11,
7,
00000800,
1011,
1,
1,
2220,
1011,
1,
1,
2220,
1011,
1,
1,
2220,
1011,
1,
1,
2220,
1,
1,
3,
11,
1,
1,
3,
60000800,
#GOGOSTART
1011,
1,
1,
2220,
1011,
1,
1,
2220,
1011,
1,
1,
2220,
1011,
1,
1,
2220,
#GOGOEND
1,
,
1,
,
11,
1,
1111,
1110,
2220,
2220,
2222,
22,
2220,
2220,
2222,
500000000000000000000000000000000000000000000008,
,
#GOGOSTART
33,
3,
1,
2220,
33,
3,
1,
2220,
1011,
1,
1,
2220,
1011,
1,
11,
1033,
#GOGOEND
,
,
#END

BIN
testing/data/butou5.zip Normal file

Binary file not shown.

View File

@ -12,6 +12,7 @@ from tja2fumen.constants import COURSE_IDS, NORMALIZE_COURSE, simpleHeaders, byt
@pytest.mark.parametrize('id_song', [
pytest.param('butou5'),
pytest.param('hol6po'),
pytest.param('mikdp'),
pytest.param('ia6cho'),