1
0
mirror of synced 2025-01-24 23:13:40 +01:00

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:
Viv 2023-06-25 21:54:12 -04:00
parent c33cd4c7e7
commit 26b23ece19
17 changed files with 35071 additions and 50 deletions

View File

@ -1,6 +1,6 @@
from copy import deepcopy 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 from tja2fumen.constants import TJA_NOTE_TYPES, DIFFICULTY_BYTES, sampleHeaderMetadata, simpleHeaders
# Filler metadata that the `writeFumen` function expects # Filler metadata that the `writeFumen` function expects
@ -213,7 +213,11 @@ def convertTJAToFumen(tja):
headerMetadata = sampleHeaderMetadata headerMetadata = sampleHeaderMetadata
headerMetadata[8] = DIFFICULTY_BYTES[tja['metadata']['course']][0] headerMetadata[8] = DIFFICULTY_BYTES[tja['metadata']['course']][0]
headerMetadata[9] = DIFFICULTY_BYTES[tja['metadata']['course']][1] 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['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['headerPadding'] = simpleHeaders[0] # Use a basic, known set of header bytes
tjaConverted['order'] = '<' tjaConverted['order'] = '<'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,51 @@
import os
import sys import sys
import struct import struct
import math import csv
def computeSoulGaugeByte(n_notes): def computeSoulGaugeBytes(n_notes, difficulty, stars):
# I don't think this is fully accurate. It doesn't work for non-Oni songs, and it's usually off by a bit. if difficulty in ['Oni', 'Ura']:
A = -85.548628 if 9 <= stars:
B = 44.780199 key = "Oni-9-10"
return round(A+B*math.log(n_notes)) 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): def readStruct(file, order, format_string, seek=None):

View File

@ -165,54 +165,33 @@ def checkValidHeader(headerBytes, strict=False):
elif idx == 9: elif idx == 9:
assert val in [27, 31, 23], f"Expected 27/31/23 at position '{idx}', got '{val}' instead." 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. # 3. Unknown (possibly related to n_notes)
# 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.
elif idx in [12, 13]: elif idx in [12, 13]:
pass pass
elif idx in [16, 17]: elif idx in [16, 17]:
pass pass
elif idx == 20:
pass
# 6. <padding> # 6. Soul gauge bytes
# Notes: # Notes:
# * For the vast majority (99%) of charts, bytes 21, 22, and 23 have the values (255, 255, 255) # * These bytes determine how quickly the soul gauge should increase
# * For a very tiny minority of charts (~5), byte 21 will be 254 or 253 instead. # * The higher the number of notes, the higher these values will be (i.e. the slower the soul gauge will rise)
# Given that most platforms use the values (255, 255, 255), and unique values are very platform-specific, # * In practice, most of the time [21, 22, 23] will be 255.
# I'm going to stick with (255, 255, 255) when it comes to converting TJA files to fumens. # * So, this means that byte 20 largely determines the soul gauge increase.
elif idx in [21, 22, 23]: # * The precise mapping between n_notes and byte values is complex, and depends on difficulty/stars.
if strict: # - See also: https://github.com/vivaria/tja2fumen/issues/14
assert val == 255, f"Expected 255 at position '{idx}', got '{val}' instead." # * Re: Byte 21, a very small number of songs (~10) have values 253 or 254 instead of 255.
else: # - This applies to Easy/Normal songs with VERY few notes (<30).
assert val in [253, 254, 255], f"Expected 253/254/255 at position '{idx}', got '{val}' instead." # - 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> # 7. <padding>
# Notes: # Notes: