diff --git a/README.md b/README.md index 0008ca0..8b017f3 100644 --- a/README.md +++ b/README.md @@ -143,27 +143,28 @@ If there is an unsupported feature that you would like support for, please make > **Legend**: `✅` = Fully supported, `⚪️` = Ignored, `⚠️` = Incorrect behavior, `❌` = Not supported -| | tja 2 fumen | tja 2 bin | Comment | -|----------------------------------------------|-------------|-----------|---------------------------------------------------------------------------------------------------------------| -| `0`, `1`, `2`, `3`, `4` | `✅` | `⚠️` | tja2fumen will write proper SENOTES (ド, コ, ドン, カ, カッ), see [#41](https://github.com/vivaria/tja2fumen/issues/41). | -| `5008,`, `6008,`, `7008,` | `✅` | `✅` | | -| `9008,` | `✅` | `⚠️` | | -| `9000,`
`9008,` | `⚪️` | `⚠️` | Double Kusudama note treated as 1 drumroll by tja2fumen, but 2 overlapping drumrolls by tja2bin. | -| `A`, `B` | `✅` | `❌` | Multiplayer "hands" notes are valid in fumens, but unrecognized by tja2bin. | -| `C`, `D`, `E`, `F`, `G`, `H`, `I` | `⚠️` | `❌` | Replaced by normal notes/rolls in tja2fumen. | -| `#START`, `#END` | `✅` | `✅` | | -| `#START P1`, `#START P2` | `✅` | `❌` | | -| `#BPMCHANGE` | `✅` | `⚠️` | See [#16](https://github.com/Fluto/TakoTako/issues/16) | -| `#MEASURE` | `✅` | `✅` | | -| `#SCROLL` | `✅` | `✅` | | -| `#GOGOSTART`, `#GOGOEND` | `✅` | `✅` | | -| `#BARLINEOFF`, `#BARLINEON` | `✅` | `✅` | | -| `#DELAY` | `✅` | `❌` | See [#27](https://github.com/Fluto/TakoTako/issues/27) | -| `#BRANCHSTART`, `#BRANCHEND` | `✅` | `✅` | | -| `#N`, `#E`, `#M` | `✅` | `✅` | | -| `#SECTION` | `⚠️` | `❌` | See [#53](https://github.com/vivaria/tja2fumen/issues/53), [#27](https://github.com/Fluto/TakoTako/issues/27) | -| `#LEVELHOLD` | `✅` | `❌` | | -| `#BMSCROLL`, `#LYRIC`,
`#DIRECTION`, etc. | `⚪️` | `❌` | Other simulator-specific chart commands are currently ignored. | +| | tja 2 fumen | tja 2 bin | Comment | +|----------------------------------------------|-----------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| `0`, `1`, `2`, `3`, `4` | `✅` | `⚠️` | tja2fumen will write proper SENOTES (ド, コ, ドン, カ, カッ), see [#41](https://github.com/vivaria/tja2fumen/issues/41). | +| `5008,`, `6008,`, `7008,` | `✅` | `✅` | | +| `9008,` | `✅` | `⚠️` | | +| `9000,`
`9008,` | `⚪️` | `⚠️` | Double Kusudama note treated as 1 drumroll by tja2fumen, but 2 overlapping drumrolls by tja2bin. | +| `A`, `B` | `✅` | `❌` | Multiplayer "hands" notes are valid in fumens, but unrecognized by tja2bin. | +| `C`, `D`, `E`, `F`, `G`, `H`, `I` | `⚠️` | `❌` | Replaced by normal notes/rolls in tja2fumen. | +| `#SENOTECHANGE` | `✅` | `❌` | Recently added. See [#69](https://github.com/vivaria/tja2fumen/issues/69) and [#81](https://github.com/vivaria/tja2fumen/issues/81) for details. | +| `#START`, `#END` | `✅` | `✅` | | +| `#START P1`, `#START P2` | `✅` | `❌` | | +| `#BPMCHANGE` | `✅` | `⚠️` | See [#16](https://github.com/Fluto/TakoTako/issues/16) | +| `#MEASURE` | `✅` | `✅` | | +| `#SCROLL` | `✅` | `✅` | | +| `#GOGOSTART`, `#GOGOEND` | `✅` | `✅` | | +| `#BARLINEOFF`, `#BARLINEON` | `✅` | `✅` | | +| `#DELAY` | `✅` | `❌` | See [#27](https://github.com/Fluto/TakoTako/issues/27) | +| `#BRANCHSTART`, `#BRANCHEND` | `✅` | `✅` | | +| `#N`, `#E`, `#M` | `✅` | `✅` | | +| `#SECTION` | `⚠️` | `❌` | See [#53](https://github.com/vivaria/tja2fumen/issues/53), [#27](https://github.com/Fluto/TakoTako/issues/27) | +| `#LEVELHOLD` | `✅` | `❌` | | +| `#BMSCROLL`, `#LYRIC`,
`#DIRECTION`, etc. | `⚪️` | `❌` | Other simulator-specific chart commands are currently ignored. | ## Reporting bugs diff --git a/src/tja2fumen/classes.py b/src/tja2fumen/classes.py index acd1247..3086b9c 100644 --- a/src/tja2fumen/classes.py +++ b/src/tja2fumen/classes.py @@ -72,6 +72,7 @@ class TJAMeasureProcessed: delay: float = 0.0 section: bool = False levelhold: bool = False + senote: str = '' branch_type: str = '' branch_cond: Tuple[float, float] = (0.0, 0.0) notes: List[TJAData] = field(default_factory=list) @@ -93,6 +94,7 @@ class FumenNote: hits: int = 0 hits_padding: int = 0 drumroll_bytes: bytes = b'\x00\x00\x00\x00\x00\x00\x00\x00' + manually_set: bool = False @dataclass() diff --git a/src/tja2fumen/constants.py b/src/tja2fumen/constants.py index 0876266..776528f 100644 --- a/src/tja2fumen/constants.py +++ b/src/tja2fumen/constants.py @@ -28,6 +28,15 @@ TJA_NOTE_TYPES = { 'I': 'Drumroll', # green roll } +# Conversion for TJAPlayer3's #SENOTECHANGE command +SENOTECHANGE_TYPES = { + 1: "Don", # ドン + 2: "Don2", # ド + 3: "Don3", # コ + 4: "Ka", # カッ + 5: "Ka2", # カ +} + # Types of notes that can be found in fumen files FUMEN_NOTE_TYPES = { 0x1: "Don", # ドン diff --git a/src/tja2fumen/converters.py b/src/tja2fumen/converters.py index 5d04300..6d21d3e 100644 --- a/src/tja2fumen/converters.py +++ b/src/tja2fumen/converters.py @@ -9,7 +9,7 @@ from typing import List, Dict, Tuple, Union from tja2fumen.classes import (TJACourse, TJAMeasure, TJAMeasureProcessed, FumenCourse, FumenHeader, FumenMeasure, FumenNote) -from tja2fumen.constants import BRANCH_NAMES +from tja2fumen.constants import BRANCH_NAMES, SENOTECHANGE_TYPES def process_commands(tja_branches: Dict[str, List[TJAMeasure]], bpm: float) \ @@ -38,6 +38,7 @@ def process_commands(tja_branches: Dict[str, List[TJAMeasure]], bpm: float) \ current_scroll = 1.0 current_gogo = False current_barline = True + current_senote = "" current_dividend = 4 current_divisor = 4 for measure_tja in branch_measures_tja: @@ -95,15 +96,18 @@ def process_commands(tja_branches: Dict[str, List[TJAMeasure]], bpm: float) \ # to BPM/SCROLL/GOGO, then the measure will actually be split # into two small submeasures. So, we need to start a new # measure in those cases.) - elif data.name in ['bpm', 'scroll', 'gogo']: + elif data.name in ['bpm', 'scroll', 'gogo', 'senote']: # Parse the values - new_val: Union[bool, float] + new_val: Union[bool, float, str] if data.name == 'bpm': new_val = current_bpm = float(data.value) elif data.name == 'scroll': new_val = current_scroll = float(data.value) elif data.name == 'gogo': new_val = current_gogo = bool(int(data.value)) + elif data.name == 'senote': + new_val = current_senote \ + = SENOTECHANGE_TYPES[int(data.value)] # Check for mid-measure commands # - Case 1: Command happens at the start of a measure; # just change the value directly @@ -123,8 +127,13 @@ def process_commands(tja_branches: Dict[str, List[TJAMeasure]], bpm: float) \ barline=current_barline, time_sig=[current_dividend, current_divisor], subdivisions=len(measure_tja.notes), - pos_start=data.pos + pos_start=data.pos, + senote=current_senote ) + # SENOTECHANGE commands don't carry over to next branch. + # (But they CAN happen mid-measure, which is why we + # process them here.) + current_senote = "" else: warnings.warn(f"Unexpected event type: {data.name}") @@ -327,7 +336,15 @@ def convert_tja_to_fumen(tja: TJACourse) -> FumenCourse: # we can initialize a note and handle general note metadata. note = FumenNote() note.pos = note_pos - note.note_type = note_tja.value + # Account for a measure's #SENOTECHANGE command + if measure_tja.senote: + note.note_type = measure_tja.senote + note.manually_set = True + # SENOTECHANGE only applies to the note immediately after + # So, we erase it once it's been applied. + measure_tja.senote = "" + else: + note.note_type = note_tja.value note.score_init = tja.score_init note.score_diff = tja.score_diff @@ -508,12 +525,13 @@ def replace_alternate_don_kas(note_clusters: List[List[FumenNote]], positions within a cluster of notes. NB: Modifies FumenNote objects in-place + NB: FumenNote values are only updated if not manually set by #SENOTECHANGE """ big_notes = ['DON', 'DON2', 'KA', 'KA2'] for cluster in note_clusters: # Replace all small notes with the basic do/ka notes ("Don2", "Ka2") for note in cluster: - if note.note_type not in big_notes: + if note.note_type not in big_notes and not note.manually_set: if note.note_type[-1].isdigit(): note.note_type = note.note_type[:-1] + "2" else: @@ -524,7 +542,8 @@ def replace_alternate_don_kas(note_clusters: List[List[FumenNote]], all_dons = all(note.note_type.startswith("Don") for note in cluster) for i, note in enumerate(cluster): if (all_dons and (len(cluster) % 2 == 1) and (i % 2 == 1) - and note.note_type not in big_notes): + and note.note_type not in big_notes + and not note.manually_set): note.note_type = "Don3" # Replace the last note in a cluster with the ending Don/Kat @@ -538,7 +557,8 @@ def replace_alternate_don_kas(note_clusters: List[List[FumenNote]], pass else: # Replace last Don2/Ka2 with Don/Ka - if cluster[-1].note_type not in big_notes: + if (cluster[-1].note_type not in big_notes + and not cluster[-1].manually_set): cluster[-1].note_type = cluster[-1].note_type[:-1] diff --git a/src/tja2fumen/parsers.py b/src/tja2fumen/parsers.py index a2b9e5f..1eef511 100644 --- a/src/tja2fumen/parsers.py +++ b/src/tja2fumen/parsers.py @@ -262,7 +262,8 @@ def parse_tja_course_data(data: List[str]) \ # 2. Parse measure commands that produce an "event" elif command in ['GOGOSTART', 'GOGOEND', 'BARLINEON', 'BARLINEOFF', 'DELAY', 'SCROLL', 'BPMCHANGE', 'MEASURE', - 'LEVELHOLD', 'SECTION', 'BRANCHSTART']: + 'LEVELHOLD', 'SENOTECHANGE', 'SECTION', + 'BRANCHSTART']: # Get position of the event pos = 0 for branch_name in (BRANCH_NAMES if current_branch == 'all' @@ -290,6 +291,8 @@ def parse_tja_course_data(data: List[str]) \ name = 'measure' elif command == 'LEVELHOLD': name = 'levelhold' + elif command == "SENOTECHANGE": + name = 'senote' elif command == 'SECTION': # If #SECTION occurs before a #BRANCHSTART, then ensure that # it's present on every branch. Otherwise, #SECTION will only diff --git a/testing/data/dummy_tjas/notes_senotechange.tja b/testing/data/dummy_tjas/notes_senotechange.tja new file mode 100644 index 0000000..3f60ae2 --- /dev/null +++ b/testing/data/dummy_tjas/notes_senotechange.tja @@ -0,0 +1,21 @@ +// This song contains only basic notes. +BPM:120 +OFFSET:-1.00 + +COURSE:Oni +LEVEL:10 +BALLOON:8,8 +SCOREINIT:400 +SCOREDIFF:100 + +#START +#SENOTECHANGE 3 +1 +#SENOTECHANGE 2 +111111, +1020304, +5000008, +6000008, +7000008, +9000008, +#END diff --git a/testing/test_command_support.py b/testing/test_command_support.py index d02598a..854cf72 100644 --- a/testing/test_command_support.py +++ b/testing/test_command_support.py @@ -13,6 +13,7 @@ from conftest import convert ['notes_double_kusudama', None], ['notes_hands', None], ['notes_sim_only', None], + ['notes_senotechange', None], ['missing_score', None], ['missing_balloon', "UserWarning"], ['missing_course', "Invalid COURSE value:"],