Start adding docstrings and fixing comments
This commit is contained in:
parent
09a293e59f
commit
fc1b0d1bbc
@ -3,12 +3,20 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from tja2fumen.parsers import parse_tja
|
from tja2fumen.parsers import parse_tja
|
||||||
from tja2fumen.writers import write_fumen
|
|
||||||
from tja2fumen.converters import convert_tja_to_fumen
|
from tja2fumen.converters import convert_tja_to_fumen
|
||||||
|
from tja2fumen.writers import write_fumen
|
||||||
from tja2fumen.constants import COURSE_IDS
|
from tja2fumen.constants import COURSE_IDS
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
|
"""
|
||||||
|
Main entry point for tja2fumen's command line interface.
|
||||||
|
|
||||||
|
Three steps are performed:
|
||||||
|
1. Parse TJA into multiple TJACourse objects. Then, for each course:
|
||||||
|
2. Convert TJACourse objects into FumenCourse objects.
|
||||||
|
3. Write each FumenCourse to its own .bin file.
|
||||||
|
"""
|
||||||
if not argv:
|
if not argv:
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
|
||||||
@ -33,6 +41,7 @@ def main(argv=None):
|
|||||||
|
|
||||||
|
|
||||||
def convert_and_write(parsed_course, base_name, single_course=False):
|
def convert_and_write(parsed_course, base_name, single_course=False):
|
||||||
|
"""Process the parsed data for a single TJA course."""
|
||||||
course_name, tja_data = parsed_course
|
course_name, tja_data = parsed_course
|
||||||
fumen_data = convert_tja_to_fumen(tja_data)
|
fumen_data = convert_tja_to_fumen(tja_data)
|
||||||
# Add course ID (e.g. '_x', '_x_1', '_x_2') to the output file's base name
|
# Add course ID (e.g. '_x', '_x_1', '_x_2') to the output file's base name
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
# Names for branches in diverge songs
|
||||||
|
BRANCH_NAMES = ("normal", "professional", "master")
|
||||||
|
|
||||||
|
# Types of notes that can be found in TJA files
|
||||||
TJA_NOTE_TYPES = {
|
TJA_NOTE_TYPES = {
|
||||||
'0': 'Blank',
|
'0': 'Blank',
|
||||||
'1': 'Don',
|
'1': 'Don',
|
||||||
@ -20,6 +24,7 @@ TJA_NOTE_TYPES = {
|
|||||||
'I': 'Drumroll', # green roll
|
'I': 'Drumroll', # green roll
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Types of notes that can be found in fumen files
|
||||||
FUMEN_NOTE_TYPES = {
|
FUMEN_NOTE_TYPES = {
|
||||||
0x1: "Don", # ドン
|
0x1: "Don", # ドン
|
||||||
0x2: "Don2", # ド
|
0x2: "Don2", # ド
|
||||||
@ -49,15 +54,17 @@ FUMEN_NOTE_TYPES = {
|
|||||||
0x22: "Unknown13", # ? (Present in some Wii1 songs)
|
0x22: "Unknown13", # ? (Present in some Wii1 songs)
|
||||||
0x62: "Drumroll2" # ?
|
0x62: "Drumroll2" # ?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Invert the dict to go from note type to fumen byte values
|
||||||
FUMEN_TYPE_NOTES = {v: k for k, v in FUMEN_NOTE_TYPES.items()}
|
FUMEN_TYPE_NOTES = {v: k for k, v in FUMEN_NOTE_TYPES.items()}
|
||||||
|
|
||||||
BRANCH_NAMES = ("normal", "professional", "master")
|
# All combinations of difficulty and single/multiplayer type
|
||||||
|
|
||||||
TJA_COURSE_NAMES = []
|
TJA_COURSE_NAMES = []
|
||||||
for difficulty in ['Ura', 'Oni', 'Hard', 'Normal', 'Easy']:
|
for difficulty in ['Ura', 'Oni', 'Hard', 'Normal', 'Easy']:
|
||||||
for player in ['', 'P1', 'P2']:
|
for player in ['', 'P1', 'P2']:
|
||||||
TJA_COURSE_NAMES.append(difficulty+player)
|
TJA_COURSE_NAMES.append(difficulty+player)
|
||||||
|
|
||||||
|
# Normalize the various fumen course names into 1 name per difficulty
|
||||||
NORMALIZE_COURSE = {
|
NORMALIZE_COURSE = {
|
||||||
'0': 'Easy',
|
'0': 'Easy',
|
||||||
'Easy': 'Easy',
|
'Easy': 'Easy',
|
||||||
@ -72,11 +79,11 @@ NORMALIZE_COURSE = {
|
|||||||
'Edit': 'Ura'
|
'Edit': 'Ura'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Map course difficulty to filename IDs (e.g. Oni -> `song_m.bin`)
|
||||||
COURSE_IDS = {
|
COURSE_IDS = {
|
||||||
'Easy': 'e',
|
'Easy': 'e',
|
||||||
'Normal': 'n',
|
'Normal': 'n',
|
||||||
'Hard': 'h',
|
'Hard': 'h',
|
||||||
'Oni': 'm',
|
'Oni': 'm',
|
||||||
'Ura': 'x',
|
'Ura': 'x',
|
||||||
'Edit': 'x'
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ from tja2fumen.constants import (NORMALIZE_COURSE, TJA_NOTE_TYPES,
|
|||||||
|
|
||||||
|
|
||||||
def parse_tja(fname_tja):
|
def parse_tja(fname_tja):
|
||||||
|
"""Read in lines of a .tja file and load them into a TJASong object."""
|
||||||
try:
|
try:
|
||||||
tja_text = open(fname_tja, "r", encoding="utf-8-sig").read()
|
tja_text = open(fname_tja, "r", encoding="utf-8-sig").read()
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
@ -28,6 +29,31 @@ def parse_tja(fname_tja):
|
|||||||
|
|
||||||
|
|
||||||
def split_tja_lines_into_courses(lines):
|
def split_tja_lines_into_courses(lines):
|
||||||
|
"""
|
||||||
|
Parse TJA metadata in order to divide TJA lines into separate courses.
|
||||||
|
|
||||||
|
In TJA files, metadata lines are denoted by a colon (':'). These lines
|
||||||
|
provide general info about the song (BPM, TITLE, OFFSET, etc.). They also
|
||||||
|
define properties for each course in the song (difficulty, level, etc.).
|
||||||
|
|
||||||
|
This function processes each line of metadata, and assigns the metadata
|
||||||
|
to TJACourse objects (one for each course). To separate each course, this
|
||||||
|
function uses the `COURSE:` metadata and any `#START P1/P2` commands,
|
||||||
|
resulting in the following structure:
|
||||||
|
|
||||||
|
TJASong
|
||||||
|
├─ TJACourse (e.g. Ura)
|
||||||
|
│ ├─ Course metadata (level, balloons, scoreinit, scorediff, etc.)
|
||||||
|
│ └─ Unparsed data (notes, commands)
|
||||||
|
├─ TJACourse (e.g. Ura-P1)
|
||||||
|
├─ TJACourse (e.g. Ura-P2)
|
||||||
|
├─ TJACourse (e.g. Oni)
|
||||||
|
├─ TJACourse (e.g. Hard)
|
||||||
|
└─ ...
|
||||||
|
|
||||||
|
The data for each TJACourse can then be parsed individually using the
|
||||||
|
`parse_tja_course_data()` function.
|
||||||
|
"""
|
||||||
parsed_tja = None
|
parsed_tja = None
|
||||||
current_course = ''
|
current_course = ''
|
||||||
current_course_cached = ''
|
current_course_cached = ''
|
||||||
@ -35,13 +61,13 @@ def split_tja_lines_into_courses(lines):
|
|||||||
song_offset = 0
|
song_offset = 0
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
# Case 1: Header metadata
|
# Case 1: Metadata lines
|
||||||
match_header = re.match(r"^([A-Z]+):(.*)", line)
|
match_metadata = re.match(r"^([A-Z]+):(.*)", line)
|
||||||
if match_header:
|
if match_metadata:
|
||||||
name_upper = match_header.group(1).upper()
|
name_upper = match_metadata.group(1).upper()
|
||||||
value = match_header.group(2).strip()
|
value = match_metadata.group(2).strip()
|
||||||
|
|
||||||
# Global header fields
|
# Global metadata fields
|
||||||
if name_upper in ['BPM', 'OFFSET']:
|
if name_upper in ['BPM', 'OFFSET']:
|
||||||
if name_upper == 'BPM':
|
if name_upper == 'BPM':
|
||||||
song_bpm = value
|
song_bpm = value
|
||||||
@ -50,7 +76,7 @@ def split_tja_lines_into_courses(lines):
|
|||||||
if song_bpm and song_offset:
|
if song_bpm and song_offset:
|
||||||
parsed_tja = TJASong(song_bpm, song_offset)
|
parsed_tja = TJASong(song_bpm, song_offset)
|
||||||
|
|
||||||
# Course-specific header fields
|
# Course-specific metadata fields
|
||||||
elif name_upper == 'COURSE':
|
elif name_upper == 'COURSE':
|
||||||
if value not in NORMALIZE_COURSE.keys():
|
if value not in NORMALIZE_COURSE.keys():
|
||||||
raise ValueError(f"Invalid COURSE value: '{value}'")
|
raise ValueError(f"Invalid COURSE value: '{value}'")
|
||||||
@ -90,7 +116,8 @@ def split_tja_lines_into_courses(lines):
|
|||||||
if match_command.group(2) else '')
|
if match_command.group(2) else '')
|
||||||
# For STYLE:Double, #START P1/P2 indicates the start of a new
|
# For STYLE:Double, #START P1/P2 indicates the start of a new
|
||||||
# chart. But, we want multiplayer charts to inherit the
|
# chart. But, we want multiplayer charts to inherit the
|
||||||
# metadata from the course as a whole, so we deepcopy.
|
# metadata from the course as a whole, so we deepcopy the
|
||||||
|
# existing course for that difficulty.
|
||||||
if name_upper == "START":
|
if name_upper == "START":
|
||||||
if value in ["P1", "P2"]:
|
if value in ["P1", "P2"]:
|
||||||
current_course = current_course_cached + value
|
current_course = current_course_cached + value
|
||||||
@ -128,9 +155,33 @@ def split_tja_lines_into_courses(lines):
|
|||||||
|
|
||||||
|
|
||||||
def parse_tja_course_data(course):
|
def parse_tja_course_data(course):
|
||||||
# Check if the course has branches or not
|
"""
|
||||||
has_branches = (True if [d for d in course.data if d.name == 'BRANCHSTART']
|
Parse course data (notes, commands) into a nested song structure.
|
||||||
else False)
|
|
||||||
|
The goal of this function is to take raw note and command strings
|
||||||
|
(e.g. '1020,', '#BPMCHANGE') and parse their values into appropriate
|
||||||
|
types (e.g. lists, ints, floats, etc.).
|
||||||
|
|
||||||
|
This function also processes measure separators (',') and branch commands
|
||||||
|
('#BRANCHSTART`, '#N`, '#E', '#M') to split the data into branches and
|
||||||
|
measures, resulting in the following structure:
|
||||||
|
|
||||||
|
TJACourse
|
||||||
|
├─ TJABranch ('normal')
|
||||||
|
│ ├─ TJAMeasure
|
||||||
|
│ │ ├─ TJAData (notes, commands)
|
||||||
|
│ │ ├─ TJAData
|
||||||
|
│ │ └─ ...
|
||||||
|
│ ├─ TJAMeasure
|
||||||
|
│ ├─ TJAMeasure
|
||||||
|
│ └─ ...
|
||||||
|
├─ TJABranch ('professional')
|
||||||
|
└─ TJABranch ('master')
|
||||||
|
|
||||||
|
This provides a faithful, easy-to-inspect tree-style representation of the
|
||||||
|
branches and measures within each course of the .tja file.
|
||||||
|
"""
|
||||||
|
has_branches = bool([d for d in course.data if d.name == 'BRANCHSTART'])
|
||||||
current_branch = 'all' if has_branches else 'normal'
|
current_branch = 'all' if has_branches else 'normal'
|
||||||
branch_condition = None
|
branch_condition = None
|
||||||
flag_levelhold = False
|
flag_levelhold = False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user