Use LUT-based method for soul gauge bytes
The new CSV files are lookup tables generated using the following script: https://github.com/vivaria/tja2fumen/issues/14#issuecomment-1606418746 Fixes #14.
This commit is contained in:
parent
c33cd4c7e7
commit
26b23ece19
@ -1,6 +1,6 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from tja2fumen.utils import computeSoulGaugeByte
|
||||
from tja2fumen.utils import computeSoulGaugeBytes
|
||||
from tja2fumen.constants import TJA_NOTE_TYPES, DIFFICULTY_BYTES, sampleHeaderMetadata, simpleHeaders
|
||||
|
||||
# Filler metadata that the `writeFumen` function expects
|
||||
@ -213,7 +213,11 @@ def convertTJAToFumen(tja):
|
||||
headerMetadata = sampleHeaderMetadata
|
||||
headerMetadata[8] = DIFFICULTY_BYTES[tja['metadata']['course']][0]
|
||||
headerMetadata[9] = DIFFICULTY_BYTES[tja['metadata']['course']][1]
|
||||
headerMetadata[20] = computeSoulGaugeByte(total_notes)
|
||||
headerMetadata[20], headerMetadata[21] = computeSoulGaugeBytes(
|
||||
n_notes=total_notes,
|
||||
difficulty=tja['metadata']['course'],
|
||||
stars=tja['metadata']['level']
|
||||
)
|
||||
tjaConverted['headerMetadata'] = b"".join(i.to_bytes(1, 'little') for i in headerMetadata)
|
||||
tjaConverted['headerPadding'] = simpleHeaders[0] # Use a basic, known set of header bytes
|
||||
tjaConverted['order'] = '<'
|
||||
|
2500
src/tja2fumen/soulgauge_LUTs/Easy-1.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Easy-1.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Easy-2-3.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Easy-2-3.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Easy-4-5.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Easy-4-5.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Hard-1-2.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Hard-1-2.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Hard-3.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Hard-3.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Hard-4.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Hard-4.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Hard-5-8.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Hard-5-8.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Normal-1-2.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Normal-1-2.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Normal-3.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Normal-3.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Normal-4.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Normal-4.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Normal-5-7.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Normal-5-7.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Oni-1-7.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Oni-1-7.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Oni-8.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Oni-8.csv
Normal file
File diff suppressed because it is too large
Load Diff
2500
src/tja2fumen/soulgauge_LUTs/Oni-9-10.csv
Normal file
2500
src/tja2fumen/soulgauge_LUTs/Oni-9-10.csv
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,51 @@
|
||||
import os
|
||||
import sys
|
||||
import struct
|
||||
import math
|
||||
import csv
|
||||
|
||||
|
||||
def computeSoulGaugeByte(n_notes):
|
||||
# I don't think this is fully accurate. It doesn't work for non-Oni songs, and it's usually off by a bit.
|
||||
A = -85.548628
|
||||
B = 44.780199
|
||||
return round(A+B*math.log(n_notes))
|
||||
def computeSoulGaugeBytes(n_notes, difficulty, stars):
|
||||
if difficulty in ['Oni', 'Ura']:
|
||||
if 9 <= stars:
|
||||
key = "Oni-9-10"
|
||||
elif stars == 8:
|
||||
key = "Oni-8"
|
||||
elif stars <= 7:
|
||||
key = "Oni-1-7"
|
||||
elif difficulty == 'Hard':
|
||||
if 5 <= stars:
|
||||
key = "Hard-5-8"
|
||||
elif stars == 4:
|
||||
key = "Hard-4"
|
||||
elif stars == 3:
|
||||
key = "Hard-3"
|
||||
elif stars <= 2:
|
||||
key = "Hard-1-2"
|
||||
elif difficulty == 'Normal':
|
||||
if 5 <= stars:
|
||||
key = "Normal-5-7"
|
||||
elif stars == 4:
|
||||
key = "Normal-4"
|
||||
elif stars == 3:
|
||||
key = "Normal-3"
|
||||
elif stars <= 2:
|
||||
key = "Normal-1-2"
|
||||
elif difficulty == 'Easy':
|
||||
if 4 <= stars:
|
||||
key = "Easy-4-5"
|
||||
elif 2 <= stars <= 3:
|
||||
key = "Easy-2-3"
|
||||
elif stars <= 1:
|
||||
key = "Easy-1"
|
||||
pkg_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
with open(os.path.join(pkg_dir, "soulgauge_LUTs", f"{key}.csv"), newline='') as csvfile:
|
||||
lut_reader = csv.reader(csvfile, delimiter=',')
|
||||
for row in lut_reader:
|
||||
if int(row[0]) == n_notes:
|
||||
soulGaugeByte20 = int(row[1]) % 255
|
||||
soulGaugeByte21 = 253 + (int(row[1]) // 255)
|
||||
return soulGaugeByte20, soulGaugeByte21
|
||||
raise ValueError(f"n_notes value '{n_notes}' not in lookup table (1-2500)")
|
||||
|
||||
|
||||
def readStruct(file, order, format_string, seek=None):
|
||||
|
@ -165,54 +165,33 @@ def checkValidHeader(headerBytes, strict=False):
|
||||
elif idx == 9:
|
||||
assert val in [27, 31, 23], f"Expected 27/31/23 at position '{idx}', got '{val}' instead."
|
||||
|
||||
# 3. TODO: Note count / drumroll count / note score / song length / etc.
|
||||
# Notes:
|
||||
# - For Oni songs, bytes (12, 16, 20) correlate with note count (bytes 13, 17 are always 0):
|
||||
# * If we look at 10* Oni songs, we see the following 2 ends of the spectrum for bytes (12, 13, 16, 17, 20):
|
||||
# - (9, 0, 4, 0, 238): Sotsu Omeshii Full (1487 notes)
|
||||
# - (9, 0, 5, 0, 237): Shimedore 2000 (1414 notes)
|
||||
# Shimedore 2000+ (1414 notes)
|
||||
# Silent Jealousy (1408 notes)
|
||||
# The Future of the Taiko Drum (1400 notes)
|
||||
# Dairouketen Maou (1396 notes)
|
||||
# - (10, 0, 5, 0, 235): Yugen no Ran (1262 notes)
|
||||
# - [...]
|
||||
# - (27, 0, 14, 0, 201): Pan vs. Gohan! Daikessen! [Normal Route] (480 notes)
|
||||
# - (28, 0, 14, 0, 200): Anata to Tu-lat-tat-ta (468 notes)
|
||||
# - (34, 0, 17, 0, 189): GeGeGe no Kitaro [6th Season] (390 notes)
|
||||
# * Just to confirm, if we look at the top/bottom 9* songs, we see:
|
||||
# - (8, 0, 4, 0, 240): Hypnosismic -Division Battle Anthem- (1608 notes)
|
||||
# - (10, 0, 8, 0, 225): Rokuchounen to Ichiyo Monogatari (846 notes)
|
||||
# - (48, 0, 24, 0, 160): Inscrutable Battle (274 notes)
|
||||
# * So, to summarize, for Oni songs:
|
||||
# - As the number of notes increases, bytes 12/16 decrease, and byte 20 increases
|
||||
# - As the number of notes decreases, bytes 12/16 increase, and byte 20 decreases
|
||||
#
|
||||
# - However, the relationship doesn't hold when checking, for example, 1* Easy charts
|
||||
# * Bytes 13 and 17, which were previously always 0, are now 0/1/2:
|
||||
# - (249, 0, 187, 0, 132): Let's go! Smile Precure (67 notes)
|
||||
# - (249, 1, 123, 1, 3): Anata to Tu-lat-tat-ta (33 notes)
|
||||
# - (44, 2, 161, 1, 234): Do you want to build a Snowman? (30 notes)
|
||||
# - (0, 1, 192, 0, 128): Odoru Ponpokorin (65 notes)
|
||||
# * I'm having trouble making sense of the relationships between these bytes.
|
||||
# 3. Unknown (possibly related to n_notes)
|
||||
elif idx in [12, 13]:
|
||||
pass
|
||||
elif idx in [16, 17]:
|
||||
pass
|
||||
elif idx == 20:
|
||||
pass
|
||||
|
||||
# 6. <padding>
|
||||
# 6. Soul gauge bytes
|
||||
# Notes:
|
||||
# * For the vast majority (99%) of charts, bytes 21, 22, and 23 have the values (255, 255, 255)
|
||||
# * For a very tiny minority of charts (~5), byte 21 will be 254 or 253 instead.
|
||||
# Given that most platforms use the values (255, 255, 255), and unique values are very platform-specific,
|
||||
# I'm going to stick with (255, 255, 255) when it comes to converting TJA files to fumens.
|
||||
elif idx in [21, 22, 23]:
|
||||
if strict:
|
||||
assert val == 255, f"Expected 255 at position '{idx}', got '{val}' instead."
|
||||
else:
|
||||
assert val in [253, 254, 255], f"Expected 253/254/255 at position '{idx}', got '{val}' instead."
|
||||
# * These bytes determine how quickly the soul gauge should increase
|
||||
# * The higher the number of notes, the higher these values will be (i.e. the slower the soul gauge will rise)
|
||||
# * In practice, most of the time [21, 22, 23] will be 255.
|
||||
# * So, this means that byte 20 largely determines the soul gauge increase.
|
||||
# * The precise mapping between n_notes and byte values is complex, and depends on difficulty/stars.
|
||||
# - See also: https://github.com/vivaria/tja2fumen/issues/14
|
||||
# * Re: Byte 21, a very small number of songs (~10) have values 253 or 254 instead of 255.
|
||||
# - This applies to Easy/Normal songs with VERY few notes (<30).
|
||||
# - For these songs, byte 20 will drop BELOW 1 and wrap around back to <=255, decrementing byte 21 by one.
|
||||
# - So, you can think of it like this:
|
||||
# * b21==253: (0*255) + 1-255 = 1-225 (VERY rapid soul gauge increase)
|
||||
# * b21==254: (1*255) + 1-255 = 256-510 (Rapid soul gauge increase)
|
||||
# * b21==255: (2*255) + 1-255 = 511-765 (Moderate to slow soul gauge increase, i.e. most songs)
|
||||
elif idx == 20:
|
||||
assert 1 <= val <= 255
|
||||
elif idx == 21:
|
||||
assert val in [253, 254, 255]
|
||||
elif idx in [22, 23]:
|
||||
assert val == 255
|
||||
|
||||
# 7. <padding>
|
||||
# Notes:
|
||||
|
Loading…
x
Reference in New Issue
Block a user