tja2fumen.py
: Add extensive comments to readFumen()
Some lines of code were rearranged for clarity purposes.
This commit is contained in:
parent
9fe0fceced
commit
ddce1b53de
@ -26,18 +26,26 @@ noteTypes = {
|
|||||||
|
|
||||||
|
|
||||||
def readFumen(fumenFile, byteOrder=None, debug=False):
|
def readFumen(fumenFile, byteOrder=None, debug=False):
|
||||||
|
"""
|
||||||
|
Parse bytes of a fumen .bin file into nested measure, branch, and note dictionaries.
|
||||||
|
|
||||||
|
For more information on any of the terms used in this function (e.g. scoreInit, scoreDiff),
|
||||||
|
please refer to KatieFrog's excellent guide: https://gist.github.com/KatieFrogs/e000f406bbc70a12f3c34a07303eec8b
|
||||||
|
"""
|
||||||
if type(fumenFile) is str:
|
if type(fumenFile) is str:
|
||||||
file = open(fumenFile, "rb")
|
file = open(fumenFile, "rb")
|
||||||
else:
|
else:
|
||||||
file = fumenFile
|
file = fumenFile
|
||||||
size = os.fstat(file.fileno()).st_size
|
size = os.fstat(file.fileno()).st_size
|
||||||
|
|
||||||
song = {}
|
# Determine:
|
||||||
|
# - The byte order (big or little endian)
|
||||||
|
# - The total number of measures from byte 0x200 (decimal 512)
|
||||||
if byteOrder:
|
if byteOrder:
|
||||||
order = ">" if byteOrder == "big" else "<"
|
order = ">" if byteOrder == "big" else "<"
|
||||||
totalMeasures = readStruct(file, order, format_string="I", seek=0x200)[0]
|
totalMeasures = readStruct(file, order, format_string="I", seek=0x200)[0]
|
||||||
else:
|
else:
|
||||||
|
# Use the number of measures to determine the byte order
|
||||||
measuresBig = readStruct(file, order="", format_string=">I", seek=0x200)[0]
|
measuresBig = readStruct(file, order="", format_string=">I", seek=0x200)[0]
|
||||||
measuresLittle = readStruct(file, order="", format_string="<I", seek=0x200)[0]
|
measuresLittle = readStruct(file, order="", format_string="<I", seek=0x200)[0]
|
||||||
if measuresBig < measuresLittle:
|
if measuresBig < measuresLittle:
|
||||||
@ -47,8 +55,15 @@ def readFumen(fumenFile, byteOrder=None, debug=False):
|
|||||||
order = "<"
|
order = "<"
|
||||||
totalMeasures = measuresLittle
|
totalMeasures = measuresLittle
|
||||||
|
|
||||||
|
# Initialize the dict that will contain the chart information
|
||||||
|
song = {}
|
||||||
|
song["length"] = totalMeasures
|
||||||
|
|
||||||
|
# Determine whether the song has branches from byte 0x1b0 (decimal 432)
|
||||||
hasBranches = getBool(readStruct(file, order, format_string="B", seek=0x1b0)[0])
|
hasBranches = getBool(readStruct(file, order, format_string="B", seek=0x1b0)[0])
|
||||||
song["branches"] = hasBranches
|
song["branches"] = hasBranches
|
||||||
|
|
||||||
|
# Print general debug metadata about the song
|
||||||
if debug:
|
if debug:
|
||||||
debugPrint("Total measures: {0}, {1} branches, {2}-endian".format(
|
debugPrint("Total measures: {0}, {1} branches, {2}-endian".format(
|
||||||
totalMeasures,
|
totalMeasures,
|
||||||
@ -56,11 +71,22 @@ def readFumen(fumenFile, byteOrder=None, debug=False):
|
|||||||
"Big" if order == ">" else "Little"
|
"Big" if order == ">" else "Little"
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Start reading measure data from position 0x208 (decimal 520)
|
||||||
file.seek(0x208)
|
file.seek(0x208)
|
||||||
for measureNumber in range(totalMeasures):
|
for measureNumber in range(totalMeasures):
|
||||||
measure = {}
|
# Parse the measure data using the following `format_string`:
|
||||||
# measureStruct: bpm 4, offset 4, gogo 1, hidden 1, dummy 2, branchInfo 4 * 6, dummy 4
|
# "ffBBHiiiiiii" (12 format characters, 40 bytes per measure)
|
||||||
|
# - 'f': BPM (represented by one float (4 bytes))
|
||||||
|
# - 'f': fumenOffset (represented by one float (4 bytes))
|
||||||
|
# - 'B': gogo (represented by one unsigned char (1 byte))
|
||||||
|
# - 'B': hidden (represented by one unsigned char (1 byte))
|
||||||
|
# - 'H': <padding> (represented by one unsigned short (2 bytes))
|
||||||
|
# - 'iiiiii': branchInfo (represented by six integers (24 bytes))
|
||||||
|
# - 'i': <padding> (represented by one integer (4 bytes)
|
||||||
measureStruct = readStruct(file, order, format_string="ffBBHiiiiiii")
|
measureStruct = readStruct(file, order, format_string="ffBBHiiiiiii")
|
||||||
|
|
||||||
|
# Create the measure dictionary using the newly-parsed measure data
|
||||||
|
measure = {}
|
||||||
measure["bpm"] = measureStruct[0]
|
measure["bpm"] = measureStruct[0]
|
||||||
measure["fumenOffset"] = measureStruct[1]
|
measure["fumenOffset"] = measureStruct[1]
|
||||||
if measureNumber == 0:
|
if measureNumber == 0:
|
||||||
@ -72,13 +98,22 @@ def readFumen(fumenFile, byteOrder=None, debug=False):
|
|||||||
measure["gogo"] = getBool(measureStruct[2])
|
measure["gogo"] = getBool(measureStruct[2])
|
||||||
measure["hidden"] = getBool(measureStruct[3])
|
measure["hidden"] = getBool(measureStruct[3])
|
||||||
|
|
||||||
for branchNumber in range(3):
|
# Iterate through the three branch types
|
||||||
branch = {}
|
for branchNumber in range(len(branchNames)):
|
||||||
# branchStruct: totalNotes 2, dummy 2, speed 4
|
# Parse the measure data using the following `format_string`:
|
||||||
|
# "HHf" (3 format characters, 8 bytes per branch)
|
||||||
|
# - 'H': totalNotes (represented by one unsigned short (2 bytes))
|
||||||
|
# - 'H': <padding> (represented by one unsigned short (2 bytes))
|
||||||
|
# - 'f': speed (represented by one float (4 bytes)
|
||||||
branchStruct = readStruct(file, order, format_string="HHf")
|
branchStruct = readStruct(file, order, format_string="HHf")
|
||||||
|
|
||||||
|
# Create the branch dictionary using the newly-parsed branch data
|
||||||
|
branch = {}
|
||||||
totalNotes = branchStruct[0]
|
totalNotes = branchStruct[0]
|
||||||
|
branch["length"] = totalNotes
|
||||||
branch["speed"] = branchStruct[2]
|
branch["speed"] = branchStruct[2]
|
||||||
|
|
||||||
|
# Print debug metadata about the branches
|
||||||
if debug and (hasBranches or branchNumber == 0 or totalNotes != 0):
|
if debug and (hasBranches or branchNumber == 0 or totalNotes != 0):
|
||||||
branchName = " ({0})".format(
|
branchName = " ({0})".format(
|
||||||
branchNames[branchNumber]
|
branchNames[branchNumber]
|
||||||
@ -94,6 +129,7 @@ def readFumen(fumenFile, byteOrder=None, debug=False):
|
|||||||
))
|
))
|
||||||
debugPrint("Total notes: {0}".format(totalNotes))
|
debugPrint("Total notes: {0}".format(totalNotes))
|
||||||
|
|
||||||
|
# Iterate through each note in the measure (per branch)
|
||||||
for noteNumber in range(totalNotes):
|
for noteNumber in range(totalNotes):
|
||||||
if debug:
|
if debug:
|
||||||
fileOffset = file.tell()
|
fileOffset = file.tell()
|
||||||
@ -103,50 +139,59 @@ def readFumen(fumenFile, byteOrder=None, debug=False):
|
|||||||
shortHex(fileOffset + 0x17)
|
shortHex(fileOffset + 0x17)
|
||||||
), end="")
|
), end="")
|
||||||
|
|
||||||
note = {}
|
# Parse the note data using the following `format_string`:
|
||||||
# noteStruct: type 4, pos 4, item 4, dummy 4, init 2, diff 2, duration 4
|
# "ififHHf" (7 format characters, 24 bytes per note cluster)
|
||||||
|
# - 'i': note type
|
||||||
|
# - 'f': note position
|
||||||
|
# - 'i': item
|
||||||
|
# - 'f': <padding>
|
||||||
|
# - 'H': scoreInit
|
||||||
|
# - 'H': scoreDiff
|
||||||
|
# - 'f': duration
|
||||||
|
# NB: 'item' doesn't seem to be used at all in this function.
|
||||||
noteStruct = readStruct(file, order, format_string="ififHHf")
|
noteStruct = readStruct(file, order, format_string="ififHHf")
|
||||||
noteType = noteStruct[0]
|
|
||||||
|
|
||||||
|
# Validate the note type
|
||||||
|
noteType = noteStruct[0]
|
||||||
if noteType not in noteTypes:
|
if noteType not in noteTypes:
|
||||||
if debug:
|
|
||||||
debugPrint("")
|
|
||||||
raise ValueError("Error: Unknown note type '{0}' at offset {1}".format(
|
raise ValueError("Error: Unknown note type '{0}' at offset {1}".format(
|
||||||
shortHex(noteType).upper(),
|
shortHex(noteType).upper(),
|
||||||
hex(file.tell() - 0x18))
|
hex(file.tell() - 0x18))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Create the note dictionary using the newly-parsed note data
|
||||||
|
note = {}
|
||||||
note["type"] = noteTypes[noteType]
|
note["type"] = noteTypes[noteType]
|
||||||
note["pos"] = noteStruct[1]
|
note["pos"] = noteStruct[1]
|
||||||
|
|
||||||
if noteType == 0xa or noteType == 0xc:
|
if noteType == 0xa or noteType == 0xc:
|
||||||
# Balloon hits
|
# Balloon hits
|
||||||
note["hits"] = noteStruct[4]
|
note["hits"] = noteStruct[4]
|
||||||
elif "scoreInit" not in song:
|
elif "scoreInit" not in song:
|
||||||
song["scoreInit"] = noteStruct[4]
|
song["scoreInit"] = noteStruct[4]
|
||||||
song["scoreDiff"] = noteStruct[5] / 4.0
|
song["scoreDiff"] = noteStruct[5] / 4.0
|
||||||
|
|
||||||
if noteType == 0x6 or noteType == 0x9 or noteType == 0xa or noteType == 0xc:
|
if noteType == 0x6 or noteType == 0x9 or noteType == 0xa or noteType == 0xc:
|
||||||
# Drumroll and balloon duration in ms
|
# Drumroll and balloon duration in ms
|
||||||
note["duration"] = noteStruct[6]
|
note["duration"] = noteStruct[6]
|
||||||
branch[noteNumber] = note
|
|
||||||
|
|
||||||
|
# Print debug information about the note
|
||||||
if debug:
|
if debug:
|
||||||
debugPrint(" ({0})".format(nameValue(note)))
|
debugPrint(" ({0})".format(nameValue(note)))
|
||||||
|
|
||||||
|
# Seek forward 8 bytes to account for padding bytes at the end of drumrolls
|
||||||
if noteType == 0x6 or noteType == 0x9 or noteType == 0x62:
|
if noteType == 0x6 or noteType == 0x9 or noteType == 0x62:
|
||||||
# Drumrolls have 8 dummy bytes at the end
|
|
||||||
file.seek(0x8, os.SEEK_CUR)
|
file.seek(0x8, os.SEEK_CUR)
|
||||||
|
|
||||||
branch["length"] = totalNotes
|
# Assign the note to the branch
|
||||||
|
branch[noteNumber] = note
|
||||||
|
|
||||||
|
# Assign the branch to the measure
|
||||||
measure[branchNames[branchNumber]] = branch
|
measure[branchNames[branchNumber]] = branch
|
||||||
|
|
||||||
|
# Assign the measure to the song
|
||||||
song[measureNumber] = measure
|
song[measureNumber] = measure
|
||||||
if file.tell() >= size:
|
if file.tell() >= size:
|
||||||
break
|
break
|
||||||
|
|
||||||
song["length"] = totalMeasures
|
|
||||||
|
|
||||||
file.close()
|
file.close()
|
||||||
return song
|
return song
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user