1
0
mirror of synced 2024-11-23 21:20:56 +01:00

Add support for #SENOTECHANGE command (#81)

TJAPlayer3 implementation details
(https://github.com/IepIweidieng/TJAPlayer3/blob/gh-pages/tja.md#senotechange):

- `#SENOTECHANGE` can be placed at the start of a measure, or in the
middle of the measure
- It will only apply to 1 note immediately after the command
- It will override any automatic Don2/Don3/Ka2 assignment (from note
clusters)

How it works in `tja2fumen`:

- `#SENOTECHANGE` will be tracked in the TJA measure object
- It will split the measure into sub-measures (similar to gogo/bpm/etc.)
- It will only be applied to the first note, after which it will be
overwritten
- We set a `manually_set` flag to ensure that the manually-chosen value
doesn't get overwritten.

Fixes #69.
This commit is contained in:
Viv 2024-10-30 00:12:26 -04:00 committed by GitHub
parent 9b6f05b420
commit 85ea4d6efc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 87 additions and 30 deletions

View File

@ -144,13 +144,14 @@ 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,`<br>`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) |

View File

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

View File

@ -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", # ドン

View File

@ -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,6 +336,14 @@ def convert_tja_to_fumen(tja: TJACourse) -> FumenCourse:
# we can initialize a note and handle general note metadata.
note = FumenNote()
note.pos = note_pos
# 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]

View File

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

View File

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

View File

@ -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:"],