1
0
mirror of synced 2024-12-24 19:54:50 +01:00
bemaniutils/bemani/format/iidxchart.py

81 lines
2.8 KiB
Python

import struct
from typing import List, Optional, Tuple
from typing_extensions import Final
class IIDXChart:
"""
Class representing a IIDX chart. This is known to be iffy with charge notes
and hell charge notes, but I never investigated enough to fix it. If somebody
wants to dig in and make a patch that would be excellent. This currently only
allows fetching notecounts and bpm since this is necessary for calculating
clear ranks for IIDX.
"""
CHART_POSITIONS: Final[List[int]] = [1, 0, 2, 7, 6, 8]
def __init__(self, data: bytes) -> None:
self.__bpm_min: Optional[int] = None
self.__bpm_max: Optional[int] = None
self.__note_counts = [0, 0, 0, 0, 0, 0]
self.__parse_charts(data)
def __parse_header(self, data: bytes) -> List[Tuple[int, int]]:
header: List[Tuple[int, int]] = []
for i in range(12):
offset, length = struct.unpack("<II", data[(i * 8) : ((i + 1) * 8)])
header.append((offset, length))
return header
def __parse_charts(self, data: bytes) -> None:
header = self.__parse_header(data)
for chart in [0, 1, 2, 3, 4, 5]:
offset, length = header[self.CHART_POSITIONS[chart]]
chartdata = data[offset : (offset + length)]
position = 0
if length == 0:
# Some songs don't have all charts :(
continue
while True:
time, event, side, value = struct.unpack(
"<iBBH", chartdata[(position * 8) : ((position + 1) * 8)]
)
position += 1
if time == 0x7FFFFFFF:
break
if event == 0 or event == 1:
# Note!
self.__note_counts[chart] += 1
if value != 0:
# Add one more for charge note lift
self.__note_counts[chart] += 1
if event == 4:
# BPM change
if value > 1000:
value = int(value / 100)
if self.__bpm_min is None:
self.__bpm_min = value
else:
self.__bpm_min = min(self.__bpm_min, value)
if self.__bpm_max is None:
self.__bpm_max = value
else:
self.__bpm_max = max(self.__bpm_max, value)
@property
def bpm(self) -> Tuple[int, int]:
if self.__bpm_min is None or self.__bpm_max is None:
raise Exception("BPM change was not found in the chart!")
return (self.__bpm_min, self.__bpm_max)
@property
def notecounts(self) -> List[int]:
return self.__note_counts