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(' 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(' 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