1
0
mirror of synced 2025-01-18 19:34:03 +01:00

Fix branching behavior for #BRANCHSTART r songs (#46)

This PR updates the behavior for songs with drumroll branching
conditions (`#BRANCHSTART r`)

The changes include:

- Correctly setting the `branchInfo` bytes for branching conditions that
occur _after_ the first condition.
- Correctly setting the `branch_points` bytes in the header when there
are only drumroll conditions.
- Correctly setting the `branch_ratio` bytes in the header when total
notes differ between branches.
- Correctly handling #SECTION commands for a number of different corner
cases:
    1. #SECTION occurs on its own without a #BRANCHSTART
    2. #BRANCHSTART occurs with a #SECTION command
3. #BRANCHSTART occurs without a #SECTION command (and is first branch
condition)
4. #BRANCHSTART occurs without a #SECTION command (and is NOT first
branch condition)


Note: I've added `shoto9` to the test suite, but the TJA file is
structured in a way that the number of measures doesn't match the number
of fumen measures. The TJA needs to be reworked, but that's okay,
because its purpose was just to check that the branching bytes were
correct.

Fixes #40.
This commit is contained in:
Viv 2023-07-19 16:47:08 -04:00 committed by GitHub
parent 2a6d1c3df5
commit 9ce1d76ca3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 753 additions and 41 deletions

View File

@ -49,12 +49,8 @@ def processTJACommands(tja):
measureTJAProcessed.delay = data.value * 1000 # ms -> s
elif data.name == 'branchStart':
measureTJAProcessed.branchStart = data.value
# If the measure immediately preceding a #BRANCHSTART has a #SECTION command, then remove it.
# From TJA spec: "Placing [a #SECTION command] near #BRANCHSTART or a measure before does not reset
# the accuracy for that branch. The value is calculated before it and a measure
# has not started yet at that point."
if tjaBranchesProcessed[branchName][-1].branchStart == ["#SECTION", -1, -1]:
tjaBranchesProcessed[branchName][-1].branchStart = None
elif data.name == 'section':
measureTJAProcessed.section = data.value
elif data.name == 'barline':
currentBarline = bool(int(data.value))
measureTJAProcessed.barline = currentBarline
@ -126,13 +122,14 @@ def convertTJAToFumen(tja):
)
# Iterate through the different branches in the TJA
total_notes = {'normal': 0, 'advanced': 0, 'master': 0}
for currentBranch, branchMeasuresTJAProcessed in processedTJABranches.items():
if not len(branchMeasuresTJAProcessed):
continue
total_notes = 0
total_notes_branch = 0
note_counter_branch = 0
currentDrumroll = None
branchConditions = []
courseBalloons = tja.balloon.copy()
# Iterate through the measures within the branch
@ -193,41 +190,67 @@ def convertTJAToFumen(tja):
if measureTJAProcessed.barline is False or (measureRatio != 1.0 and measureTJAProcessed.pos_start != 0):
measureFumen.barline = False
# If a #SECTION command occurs in isolation, and it has a valid condition, then treat it like a branchStart
if (measureTJAProcessed.section is not None and measureTJAProcessed.section != 'not_available'
and not measureTJAProcessed.branchStart):
branchCondition = measureTJAProcessed.section
else:
branchCondition = measureTJAProcessed.branchStart
# Check to see if the measure contains a branching condition
if measureTJAProcessed.branchStart:
if branchCondition:
# Determine which values to assign based on the type of branching condition
if measureTJAProcessed.branchStart[0] == 'p':
if branchCondition[0] == 'p':
vals = []
for percent in measureTJAProcessed.branchStart[1:]:
# Ensure percentage is actually a percentage value
for percent in branchCondition[1:]:
# Ensure percentage is between 0% and 100%
if 0 <= percent <= 1:
val = total_notes_branch * percent * 20
# If the result is very close, then round to account for lack of precision in percentage
if abs(val - round(val)) < 0.1:
val = round(val)
vals.append(int(val))
# If it isn't a percentage value, then pass it back as-is
# If it is above 100%, then it means a guaranteed "level down". Fumens use 999 for this.
elif percent > 1:
vals.append(999)
# If it is below 0%, it is a guaranteed "level up". Fumens use 0 for this.
else:
vals.append(int(percent * 100))
# If it's a drumroll then use the branch condition values as-is
elif measureTJAProcessed.branchStart[0] == 'r':
vals = measureTJAProcessed.branchStart[1:]
# If it's a #SECTION command, use the branch condition values as-is AND reset the accuracy
elif measureTJAProcessed.branchStart[0] == '#SECTION':
vals = measureTJAProcessed.branchStart[1:]
note_counter_branch = 0
# Determine which bytes to assign the values to
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
# Assign the values to their intended bytes
measureFumen.branchInfo[idx_b1] = vals[0]
measureFumen.branchInfo[idx_b2] = vals[1]
vals.append(0)
if currentBranch == 'normal':
measureFumen.branchInfo[0:2] = vals
elif currentBranch == 'advanced':
measureFumen.branchInfo[2:4] = vals
elif currentBranch == 'master':
measureFumen.branchInfo[4:6] = vals
# If it's a drumroll, then the values to use depends on whether there is a #SECTION in the same measure
# - If there is a #SECTION, then accuracy is reset, so repeat the same condition for all 3 branches
# - If there isn't a #SECTION, but it's the first branch condition, repeat for all 3 branches as well
# - If there isn't a #SECTION, and there are previous branch conditions, the outcomes now matter:
# * If the player failed to go from Normal -> Advanced/Master, then they must stay in Normal,
# hence the 999 values (which force them to stay in Normal)
# * If the player made it to Advanced, then both condition values still apply (for either
# staying in Advanced or leveling up to Master)
# * If the player made it to Master, then only use the "master condition" value (2), otherwise
# they fall back to Normal.
# - The "no-#SECTION" behavior can be seen in songs like "Shoutoku Taiko no 「Hi Izuru Made Asuka」"
elif branchCondition[0] == 'r':
if currentBranch == 'normal':
measureFumen.branchInfo[0:2] = (branchCondition[1:] if measureTJAProcessed.section or
not measureTJAProcessed.section and not branchConditions
else [999, 999])
elif currentBranch == 'advanced':
measureFumen.branchInfo[2:4] = branchCondition[1:]
elif currentBranch == 'master':
measureFumen.branchInfo[4:6] = (branchCondition[1:] if measureTJAProcessed.section or
not measureTJAProcessed.section and not branchConditions
else [branchCondition[2]] * 2)
# Reset the note counter corresponding to this branch (i.e. reset the accuracy)
total_notes_branch = 0
# Cache branch conditions, but skip conditions where the only purpose is to level down to 'normal'
if measureFumen.branchInfo != [999, 999, 999, 999, 999, 999]:
branchConditions.append(branchCondition)
# NB: We update the branch condition note counter *after* we check the current measure's branch condition.
# This is because the TJA spec says:
@ -271,10 +294,10 @@ def convertTJAToFumen(tja):
if note.type in ["Balloon", "Kusudama"]:
note.hits = courseBalloons.pop(0)
currentDrumroll = note
total_notes -= 1
total_notes[currentBranch] -= 1
if note.type in ["Drumroll", "DRUMROLL"]:
currentDrumroll = note
total_notes -= 1
total_notes[currentBranch] -= 1
# Count dons, kas, and balloons for the purpose of tracking branching accuracy
if note.type.lower() in ['don', 'ka']:
note_counter_branch += 1
@ -292,11 +315,26 @@ def convertTJAToFumen(tja):
currentDrumroll.duration += measureDuration
measureFumen.branches[currentBranch].length = note_counter
total_notes += note_counter
total_notes[currentBranch] += note_counter
# Set song-specific metadata
fumen.header.b512_b515_number_of_measures = len(fumen.measures)
fumen.header.b432_b435_has_branches = int(all([len(b) for b in processedTJABranches.values()]))
fumen.header.set_hp_bytes(total_notes, tja.course, tja.level)
# If song has only drumroll branching conditions, then only drumrolls should contribute to branching
if all([condition[0] == 'r' for condition in branchConditions]):
fumen.header.b468_b471_branch_points_good = 0
fumen.header.b484_b487_branch_points_good_BIG = 0
fumen.header.b472_b475_branch_points_ok = 0
fumen.header.b488_b491_branch_points_ok_BIG = 0
fumen.header.b496_b499_branch_points_balloon = 0
fumen.header.b500_b503_branch_points_kusudama = 0
# Compute the ratio between normal and advanced/master branches (just in case the note counts differ)
if total_notes['advanced']:
fumen.header.b460_b463_normal_professional_ratio = int(65536 * (total_notes['normal'] / total_notes['advanced']))
if total_notes['master']:
fumen.header.b464_b467_normal_master_ratio = int(65536 * (total_notes['normal'] / total_notes['master']))
return fumen

View File

@ -119,7 +119,7 @@ def parseCourseMeasures(course):
# Process course lines
idx_m = 0
idx_m_branchstart = 0
for line in course.data:
for idx_l, line in enumerate(course.data):
# 1. Parse measure notes
if line.name == 'NOTES':
notes = line.value
@ -159,13 +159,15 @@ def parseCourseMeasures(course):
elif line.name == 'MEASURE':
currentEvent = TJAData('measure', line.value, pos)
elif line.name == 'SECTION':
# If #SECTION occurs before the first #BRANCHSTART condition, then we have no percentage/drumroll values
# to use for the branchInfo bytes when writing to the fumen. So, we just use default values (-1, -1).
if branch_condition is None:
branch_condition = ['#SECTION', -1, -1]
# Otherwise, if #SECTION occurs after a #BRANCHSTART condition, then we just repeat the previous
# condition (to set the correct branchInfo bytes for this measure.)
currentEvent = TJAData('branchStart', branch_condition, pos)
currentEvent = TJAData('section', 'not_available', pos)
else:
currentEvent = TJAData('section', branch_condition, pos)
# If the command immediately after #SECTION is #BRANCHSTART, then we need to make sure that #SECTION
# is put on every branch. (We can't do this unconditionally because #SECTION commands can also exist
# in isolation in the middle of separate branches.)
if course.data[idx_l+1].name == 'BRANCHSTART':
currentBranch = 'all'
elif line.name == 'BRANCHSTART':
if flagLevelhold:
continue

View File

@ -47,7 +47,7 @@ class TJAMeasure:
class TJAMeasureProcessed:
def __init__(self, bpm, scroll, gogo, barline, time_sig, subdivisions,
pos_start=0, pos_end=0, delay=0, branchStart=None, data=None):
pos_start=0, pos_end=0, delay=0, section=None, branchStart=None, data=None):
self.bpm = bpm
self.scroll = scroll
self.gogo = gogo
@ -57,6 +57,7 @@ class TJAMeasureProcessed:
self.pos_start = pos_start
self.pos_end = pos_end
self.delay = delay
self.section = section
self.branchStart = branchStart
self.data = [] if data is None else data

670
testing/data/shoto9.tja Normal file
View File

@ -0,0 +1,670 @@
BPM:100
OFFSET:-2.513
COURSE:Oni
LEVEL:9
BALLOON:10,5,5,5,10
SCOREINIT:400
SCOREDIFF:95
#START
100202100200100202102200,
100202100200000202102200,
100202100200100202122202,
#BPMCHANGE 160
#SCROLL 0.62
7008,
#SCROLL 1
2000200020112210,
3001201002112210,
1001201002112211,
1001201002112210,
1021001002012222,
1001201002112210,
1001201002112211,
1001001001002210,
3000000022010020,
12022022,
500000000008000000200200100000000100000000100000,
2021002020102020,
#BRANCHSTART r,5,6
#N
100000000100000000200000500000000008000000100200,
1000202210201120,
#E
100000000100000000200000500000000008000000100200,
1000202210201120,
#M
100000000100000000200000500000000008000000100200,
1000202210201120,
#BRANCHSTART r,7,8
#BRANCHEND
500000000008000000200200100000200200200200200200,
1010202210201020,
#BRANCHSTART r,4,5
#N
1001001070080000,
100
#SCROLL 1
00
#SCROLL 1
0
#SCROLL 1
1
#SCROLL 1
00
#SCROLL 1
0
#SCROLL 1
10200
#SCROLL 1
0,
#SCROLL 1
10
#SCROLL 1
0
#SCROLL 1
10
#SCROLL 1
0
#SCROLL 1
1
#SCROLL 1
0
#SCROLL 1
00
#SCROLL 1
1020
#SCROLL 1
0
#SCROLL 1
0,
#SCROLL 1
1
#SCROLL 1
0
#SCROLL 1
0
#SCROLL 1
000
#SCROLL 1
1
#SCROLL 1
00
#SCROLL 1
0
#SCROLL 1
10200
#SCROLL 1
0,
#SCROLL 1
1001002000304000,
#E
1001001070080000,
100
#SCROLL 1.25
20
#SCROLL 1.5
2
#SCROLL 1
1
#SCROLL 1.25
20
#SCROLL 1.5
2
#SCROLL 1
10200
#SCROLL 1.25
1,
#SCROLL 1
10
#SCROLL 1.5
2
#SCROLL 1
10
#SCROLL 1.25
2
#SCROLL 1
1
#SCROLL 1.5
2
#SCROLL 1.25
20
#SCROLL 1
1020
#SCROLL 1.25
2
#SCROLL 1.5
1,
#SCROLL 1
1
#SCROLL 1.25
2
#SCROLL 1.5
2
#SCROLL 1.25
202
#SCROLL 1
1
#SCROLL 1.25
20
#SCROLL 1.5
2
#SCROLL 1
10200
#SCROLL 1.25
1,
#SCROLL 1
1001002000304000,
#M
1001001070080000,
122
#SCROLL 1
22
#SCROLL 1
0
#SCROLL 1.5
1
#SCROLL 1.5
20
#SCROLL 1.5
2
#SCROLL 1.5
10212
#SCROLL 1.5
0,
#SCROLL 1.5
12
#SCROLL 1.5
2
#SCROLL 1.5
12
#SCROLL 1.5
2
#SCROLL 1.5
1
#SCROLL 1.5
2
#SCROLL 1.5
02
#SCROLL 1.5
1022
#SCROLL 1.5
2
#SCROLL 1.5
2,
#SCROLL 1.5
1
#SCROLL 1.5
2
#SCROLL 1.5
2
#SCROLL 1.5
220
#SCROLL 1.5
1
#SCROLL 1.5
20
#SCROLL 1.5
2
#SCROLL 1.5
11212
#SCROLL 1.5
2,
#SCROLL 1.5
1221001000304000,
#BRANCHSTART p,101,102
#BRANCHEND
1020102210201022,
1022122010201022,
1000201110002011,
1010200010210010,
1022102010221020,
1100001100000000,
#GOGOSTART
3021002010201120,
1021002010201122,
1020102210201020,
0020201022112211,
1021002010201120,
1021002010201122,
1022102010221120,
11110340,
1020112010201120,
1021012010201120,
1020112010201120,
0020201022112121,
1021012010221020,
3003003000000020,
1020112030030030,
0000002022020020,
#GOGOEND
1001201000201121,
1001201000201122,
1001201002211212,
1001001000000000,
#BPMCHANGE 200
#SCROLL 0.8
3004,
#SCROLL 1
1020112010120211,
1020112011210122,
1020112010120210,
1120112010221120,
1020112010120211,
1020112011211202,
1020112010112010,
1012221121112222,
1020112010120211,
1020112011211004,
0004000400000000,
1022102210222222,
1020112010112010,
1020101122222010,
2211102211112210,
#BPMCHANGE 160
#SCROLL 1.25
3,
#SCROLL 1
#GOGOSTART
3000000000100220,
30000122,
3020102210201020,
0020102211221212,
1021002010201120,
1021002010201120,
1022102010221212,
11110340,
1020112010201120,
1021012010201120,
1020112010201120,
0020201022112121,
1021012010221020,
3003003000000020,
1020112030030030,
0000002022020020,
#GOGOEND
3001201002112210,
1001201002112212,
1001201002112210,
1021001002012222,
1001201002112210,
1001201002112212,
1001001001002210,
3700,
8300,
#END
COURSE:Hard
LEVEL:7
BALLOON:10,5,8
SCOREINIT:
SCOREDIFF:
#START
100200000200100200102200,
100200000200100200102200,
100200000200100202200200,
#MEASURE 5/8
70008,
#BPMCHANGE 160
#MEASURE 4/4
2000200020202220,
3001002002001110,
1001002002002220,
1001002002002220,
1001001001004000,
1001002002001110,
1001002002002220,
1001001001002220,
3,
30002022,
500000000008000000100100100000000000000000000000,
30022220,
500000000000000008000000300000000000000000000000,
0000300010201110,
1020102010202220,
1,
300000000300000000300000600000000000000008000000,
10010120,
1001001000102000,
10010120,
1001001000304000,
1000102010201110,
1000102010202220,
1000201110002011,
1010200040040040,
600000000000000000000000000000000008000000000000,
1100001100000000,
#GOGOSTART
3000102010201110,
1000102010201110,
1000102010222010,
0020201022202220,
1000102010201110,
1000102010201110,
1022202010222020,
11110440,
1020111010201110,
1020111010201110,
1020111010222010,
0020201022202220,
1020111010202220,
3003003000000000,
1020111030030030,
,
#GOGOEND
1001002002001110,
1001002002002220,
1001002002002220,
1001007000000800,
#BPMCHANGE 200
#SCROLL 0.8
3,
#SCROLL 1
10101110,
1000100010101110,
10101110,
1010111011101000,
10101110,
1000100010101110,
10101110,
1010111011101000,
10101110,
1000100011101000,
,
1000100010101110,
10101110,
1000100011101000,
1010101010101110,
#BPMCHANGE 160
#SCROLL 1.25
3,
#SCROLL 1
#GOGOSTART
3000000000200200,
3002,
3000102010222010,
0020201022202220,
1000102010201110,
1000102010201110,
1022202010222020,
11110440,
1020111010201110,
1020111010201110,
1020111010222010,
0020201022202220,
1020111010201110,
3003003000000000,
1020111030030030,
,
#GOGOEND
3001002002001110,
1001002002002220,
1001002002002220,
1001001001004000,
1001002002001110,
1001002002002220,
1001002002002020,
3700,
8300,
,
#END
COURSE:Normal
LEVEL:6
BALLOON:4
SCOREINIT:
SCOREDIFF:
#START
0111,
500000000008000000100000000000000000000000000000,
0111,
#MEASURE 5/8
500000000000000000000008000000,
#BPMCHANGE 160
#MEASURE 4/4
,
10200120,
10200120,
10200120,
3003003003003000,
10200120,
10200120,
1111,
3,
3022,
500000000008000000000000300000000000000000000000,
1022,
500000000008000000000000300000000000000000000000,
1012,
100000100000200000000000500000000008000000000000,
1,
3003003030000000,
10010020,
10010220,
10010020,
10010340,
10121010,
10121010,
3434,
3000400040040040,
600000000000000000000000000000000008000000000000,
30030000,
#GOGOSTART
10121210,
10121210,
10121212,
0440,
10121210,
10121210,
500000000000000008000000500000000000000008000000,
33330440,
12121210,
12121210,
12121222,
0440,
10121210,
3003003000000000,
1020100030030030,
,
#GOGOEND
10210120,
10210120,
10210120,
1001007000000800,
#BPMCHANGE 200
#SCROLL 0.8
3,
#SCROLL 1
10001110,
11101010,
10001110,
11111110,
10001110,
11101010,
10001110,
3333,
10001110,
11101010,
,
3333,
10001110,
10001110,
1111,
#BPMCHANGE 160
#SCROLL 1.25
3,
#SCROLL 1
#GOGOSTART
30000400,
3004,
10121212,
0440,
10121210,
10121210,
500000000000000008000000500000000000000008000000,
33330440,
12121210,
12121210,
12121222,
0440,
10121210,
3003003000000000,
1020100030030030,
,
#GOGOEND
10210120,
10210120,
10210120,
3003003003003000,
10210120,
10210120,
1111,
3,
0300,
,
#END
COURSE:Easy
LEVEL:4
BALLOON:5,8,4,5
SCOREINIT:
SCOREDIFF:
#START
11,
500000000000000000000008000000000000000000000000,
11,
#MEASURE 5/8
500000000000000000000008000000,
#BPMCHANGE 160
#MEASURE 4/4
,
1011,
1011,
1011,
500000000000000000000000000008000000000000000000,
1011,
1011,
1011,
3,
1,
500000000000000000000000000008000000000000000000,
1,
500000000000000000000000000008000000000000000000,
11,
1110,
3,
70000800,
1,
12,
1,
14,
1011,
1022,
2303,
0300,
9009,
8,
#GOGOSTART
1011,
1022,
20001005,
000000000000000000000000000008000000000000000000,
1011,
1022,
2022,
70000800,
1011,
1022,
20001005,
000000000000000000000000000008000000000000000000,
1011,
10030000,
10101003,
,
#GOGOEND
1,
12,
1,
10010000,
#BPMCHANGE 200
#SCROLL 0.8
3,
#SCROLL 1
1,
1110,
1,
2220,
1,
1110,
1,
2222,
1,
1110,
,
2222,
1,
1110,
70000800,
#BPMCHANGE 160
#SCROLL 1.25
3,
#SCROLL 1
#GOGOSTART
10000100,
1,
10001005,
000000000000000000000000000008000000000000000000,
1011,
1022,
2011,
10030000,
1011,
1022,
20001005,
000000000000000000000000000008000000000000000000,
1011,
10030000,
10102003,
,
#GOGOEND
1011,
1011,
1022,
500000000000000000000000000008000000000000000000,
1011,
1011,
1022,
3,
0300,
,
#END

BIN
testing/data/shoto9.zip Normal file

Binary file not shown.

View File

@ -12,6 +12,7 @@ from tja2fumen.constants import COURSE_IDS, NORMALIZE_COURSE
@pytest.mark.parametrize('id_song', [
pytest.param('shoto9', marks=pytest.mark.skip("TJA structure does not match fumen yet.")),
pytest.param('genpe'),
pytest.param('gimcho'),
pytest.param('imcanz'),