1
0
mirror of synced 2025-01-24 15:12:19 +01:00

Start adding docstrings and fixing comments

This commit is contained in:
Viv 2023-07-25 21:50:12 -04:00
parent 09a293e59f
commit fc1b0d1bbc
3 changed files with 82 additions and 15 deletions

View File

@ -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

View File

@ -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'
} }

View File

@ -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