more .memon format support
This commit is contained in:
parent
3a0110ea07
commit
0056264d41
@ -14,9 +14,9 @@ from itertools import chain
|
||||
|
||||
from path import Path
|
||||
import simplejson as json
|
||||
from marshmallow import Schema, fields, RAISE, validate, validates_schema, ValidationError
|
||||
from marshmallow import Schema, fields, RAISE, validate, validates_schema, ValidationError, post_load
|
||||
|
||||
from jubeatools.song import Song, BPMChange, TapNote, LongNote
|
||||
from jubeatools.song import *
|
||||
from jubeatools.utils import lcm
|
||||
|
||||
# v0.x.x long note value :
|
||||
@ -135,18 +135,123 @@ def _search_and_load(file_or_folder: Path) -> Any:
|
||||
return json.load(open(file_path), use_decimal=True)
|
||||
|
||||
|
||||
def _load_memon_note_v0(note: dict, resolution: int) -> Union[TapNote, LongNote]:
|
||||
position = NotePosition.from_index(note["n"])
|
||||
time = BeatsTime.from_ticks(ticks=note["t"], resolution=resolution)
|
||||
if (note["l"] > 0):
|
||||
duration = BeatsTime.from_ticks(ticks=note["l"], resolution=resolution)
|
||||
tail_tip = NotePosition(*P_VALUE_TO_X_Y_OFFSET[note["p"]])
|
||||
return LongNote(time, position, duration, tail_tip)
|
||||
else:
|
||||
return TapNote(time, position)
|
||||
|
||||
|
||||
|
||||
def load_memon_legacy(file_or_folder: Path) -> Song:
|
||||
memon = _search_and_load(file_or_folder)
|
||||
raw_memon = _search_and_load(file_or_folder)
|
||||
schema = Memon_legacy()
|
||||
memon = schema.load(raw_memon)
|
||||
metadata = Metadata(
|
||||
**{
|
||||
key: memon["metadata"][key]
|
||||
for key in ["title", "artist", "audio", "cover"]
|
||||
}
|
||||
)
|
||||
global_timing = Timing(
|
||||
events=[BPMChange(time=0, BPM=memon["metadata"]["BPM"])],
|
||||
beat_zero_offset=SecondsTime(-memon["metadata"]["offset"])
|
||||
)
|
||||
charts: Mapping[str, Chart] = MultiDict()
|
||||
for memon_chart in memon["data"]:
|
||||
charts.add(
|
||||
memon_chart["dif_name"],
|
||||
Chart(
|
||||
level=memon_chart["level"],
|
||||
notes=[
|
||||
_load_memon_note_v0(note, memon_chart["resolution"])
|
||||
for note in memon_chart["notes"]
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return Song(
|
||||
metadata=metadata,
|
||||
charts=charts,
|
||||
global_timing=global_timing
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
def load_memon_0_1_0(file_or_folder: Path) -> Song:
|
||||
...
|
||||
raw_memon = _search_and_load(file_or_folder)
|
||||
schema = Memon_0_1_0()
|
||||
memon = schema.load(raw_memon)
|
||||
metadata = Metadata(
|
||||
**{
|
||||
key: memon["metadata"][key]
|
||||
for key in ["title", "artist", "audio", "cover"]
|
||||
}
|
||||
)
|
||||
global_timing = Timing(
|
||||
events=[BPMChange(time=0, BPM=memon["metadata"]["BPM"])],
|
||||
beat_zero_offset=SecondsTime(-memon["metadata"]["offset"])
|
||||
)
|
||||
charts: Mapping[str, Chart] = MultiDict()
|
||||
for difficulty, memon_chart in memon["data"]:
|
||||
charts.add(
|
||||
difficulty,
|
||||
Chart(
|
||||
level=memon_chart["level"],
|
||||
notes=[
|
||||
_load_memon_note_v0(note, memon_chart["resolution"])
|
||||
for note in memon_chart["notes"]
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return Song(
|
||||
metadata=metadata,
|
||||
charts=charts,
|
||||
global_timing=global_timing
|
||||
)
|
||||
|
||||
|
||||
def load_memon_0_2_0(file_or_folder: Path) -> Song:
|
||||
...
|
||||
raw_memon = _search_and_load(file_or_folder)
|
||||
schema = Memon_0_2_0()
|
||||
memon = schema.load(raw_memon)
|
||||
metadata_dict = {
|
||||
key: memon["metadata"][key]
|
||||
for key in ["title", "artist", "audio", "cover"]
|
||||
}
|
||||
if "preview" in memon["metadata"]:
|
||||
metadata_dict["preview_start"] = memon["metadata"]["preview"]["position"]
|
||||
metadata_dict["preview_length"] = memon["metadata"]["preview"]["length"]
|
||||
|
||||
metadata = Metadata(**metadata_dict)
|
||||
global_timing = Timing(
|
||||
events=[BPMChange(time=0, BPM=memon["metadata"]["BPM"])],
|
||||
beat_zero_offset=SecondsTime(-memon["metadata"]["offset"])
|
||||
)
|
||||
charts: Mapping[str, Chart] = MultiDict()
|
||||
for difficulty, memon_chart in memon["data"]:
|
||||
charts.add(
|
||||
difficulty,
|
||||
Chart(
|
||||
level=memon_chart["level"],
|
||||
notes=[
|
||||
_load_memon_note_v0(note, memon_chart["resolution"])
|
||||
for note in memon_chart["notes"]
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return Song(
|
||||
metadata=metadata,
|
||||
charts=charts,
|
||||
global_timing=global_timing
|
||||
)
|
||||
|
||||
|
||||
def _long_note_tail_value_v0(note: LongNote) -> int:
|
||||
@ -206,20 +311,19 @@ def _compute_resolution(notes: List[Union[TapNote, LongNote]]) -> int:
|
||||
)
|
||||
|
||||
|
||||
def _iter_dump_notes_v0(resolution: int, notes: List[Union[TapNote, LongNote]]) -> Iterable[Dict[str, int]]:
|
||||
"""Iterable that converts notes into the {n, t, l, p} form"""
|
||||
for note in sorted(set(notes), key=lambda n: (n.time, n.position)):
|
||||
memon_note = {
|
||||
"n": note.index,
|
||||
"t": note.time.numerator * (resolution // note.time.denominator),
|
||||
"l": 0,
|
||||
"p": 0
|
||||
}
|
||||
if isinstance(note, LongNote):
|
||||
memon_note["l"] = note.duration.numerator * (resolution // note.duration.denominator)
|
||||
memon_note["p"] = _long_note_tail_value_v0(note)
|
||||
|
||||
yield memon_note
|
||||
def _dump_memon_note_v0(note: Union[TapNote, LongNote], resolution: int) -> Dict[str, int]:
|
||||
"""converts a note into the {n, t, l, p} form"""
|
||||
memon_note = {
|
||||
"n": note.index,
|
||||
"t": note.time.numerator * (resolution // note.time.denominator),
|
||||
"l": 0,
|
||||
"p": 0
|
||||
}
|
||||
if isinstance(note, LongNote):
|
||||
memon_note["l"] = note.duration.numerator * (resolution // note.duration.denominator)
|
||||
memon_note["p"] = _long_note_tail_value_v0(note)
|
||||
|
||||
return memon_note
|
||||
|
||||
|
||||
def dump_memon_legacy(song: Song) -> Iterable[Tuple[Any, IO]]:
|
||||
@ -233,7 +337,7 @@ def dump_memon_legacy(song: Song) -> Iterable[Tuple[Any, IO]]:
|
||||
"music path": str(song.metadata.audio),
|
||||
"jacket path": str(song.metadata.cover),
|
||||
"BPM": song.global_timing.events[0].BPM,
|
||||
"offset": song.global_timing.beat_zero_offset
|
||||
"offset": -song.global_timing.beat_zero_offset
|
||||
},
|
||||
"data": []
|
||||
}
|
||||
@ -243,7 +347,10 @@ def dump_memon_legacy(song: Song) -> Iterable[Tuple[Any, IO]]:
|
||||
"dif_name": difficulty,
|
||||
"level": chart.level,
|
||||
"resolution": resolution,
|
||||
"notes": list(_iter_dump_notes_v0(resolution, chart.notes))
|
||||
"notes": [
|
||||
_dump_memon_note_v0(note, resolution)
|
||||
for note in sorted(set(chart.notes), key=lambda n: (n.time, n.position))
|
||||
]
|
||||
})
|
||||
|
||||
return [(song, _dump_to_json(memon))]
|
||||
@ -261,7 +368,7 @@ def dump_memon_0_1_0(song: Song, folder: Path) -> None:
|
||||
"music path": str(song.metadata.audio),
|
||||
"album cover path": str(song.metadata.cover),
|
||||
"BPM": song.global_timing.events[0].BPM,
|
||||
"offset": song.global_timing.beat_zero_offset
|
||||
"offset": -song.global_timing.beat_zero_offset
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
@ -270,7 +377,10 @@ def dump_memon_0_1_0(song: Song, folder: Path) -> None:
|
||||
memon["data"][difficulty] = {
|
||||
"level": chart.level,
|
||||
"resolution": resolution,
|
||||
"notes": list(_iter_dump_notes_v0(resolution, chart.notes))
|
||||
"notes": [
|
||||
_dump_memon_note_v0(note, resolution)
|
||||
for note in sorted(set(chart.notes), key=lambda n: (n.time, n.position))
|
||||
]
|
||||
}
|
||||
|
||||
return [(song, _dump_to_json(memon))]
|
||||
@ -288,7 +398,7 @@ def dump_memon_0_2_0(song: Song, folder: Path) -> None:
|
||||
"music path": str(song.metadata.audio),
|
||||
"album cover path": str(song.metadata.cover),
|
||||
"BPM": song.global_timing.events[0].BPM,
|
||||
"offset": song.global_timing.beat_zero_offset,
|
||||
"offset": -song.global_timing.beat_zero_offset,
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
@ -304,7 +414,10 @@ def dump_memon_0_2_0(song: Song, folder: Path) -> None:
|
||||
memon["data"][difficulty] = {
|
||||
"level": chart.level,
|
||||
"resolution": resolution,
|
||||
"notes": list(_iter_dump_notes_v0(resolution, chart.notes))
|
||||
"notes": [
|
||||
_dump_memon_note_v0(note, resolution)
|
||||
for note in sorted(set(chart.notes), key=lambda n: (n.time, n.position))
|
||||
]
|
||||
}
|
||||
|
||||
return [(song, _dump_to_json(memon))]
|
@ -7,18 +7,22 @@ Precision-critical times are stored as a fraction of beats,
|
||||
otherwise a decimal number of seconds can be used
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from collections import namedtuple, UserList
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
from typing import List, Optional, Union, Mapping
|
||||
from typing import List, Optional, Union, Mapping, Type
|
||||
|
||||
from path import Path
|
||||
from multidict import MultiDict
|
||||
|
||||
|
||||
class BeatsTime(Fraction):
|
||||
...
|
||||
@classmethod
|
||||
def from_ticks(cls: Type[Fraction], ticks: int, resolution: int) -> 'BeatsTime':
|
||||
if resolution < 1:
|
||||
raise ValueError(f"resolution cannot be negative : {resolution}")
|
||||
return cls(ticks, resolution)
|
||||
|
||||
|
||||
class SecondsTime(Decimal):
|
||||
@ -33,6 +37,13 @@ class NotePosition:
|
||||
@property
|
||||
def index(self):
|
||||
return self.x + 4*self.y
|
||||
|
||||
@classmethod
|
||||
def from_index(cls: Type[NotePosition], index: int) -> 'NotePosition':
|
||||
if not (0 <= index < 16):
|
||||
raise ValueError(f"Note position index out of range : {index}")
|
||||
|
||||
return cls(x = index%4, y = index//4)
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -73,7 +84,7 @@ class Timing:
|
||||
@dataclass
|
||||
class Chart:
|
||||
level: Decimal
|
||||
timing: Optional[Timing]
|
||||
timing: Optional[Timing] = None
|
||||
notes: List[Union[TapNote, LongNote]]
|
||||
|
||||
|
||||
@ -83,16 +94,16 @@ class Metadata:
|
||||
artist: str
|
||||
audio: Path
|
||||
cover: Path
|
||||
preview_start: SecondsTime
|
||||
preview_length: SecondsTime
|
||||
preview_start: SecondsTime = SecondsTime(0)
|
||||
preview_length: SecondsTime = SecondsTime(0)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Song:
|
||||
"""
|
||||
The abstract representation format for all jubeat chart sets.
|
||||
A Song is a set of charts with associated metadata
|
||||
"""
|
||||
def __init__(self):
|
||||
self.metadata = Metadata()
|
||||
self.charts : Mapping[str, Chart] = MultiDict()
|
||||
self.global_timing : Optional[Timing] = Timing()
|
||||
|
||||
"""The abstract representation format for all jubeat chart sets.
|
||||
A Song is a set of charts with associated metadata"""
|
||||
|
||||
metadata: Metadata
|
||||
charts: Mapping[str, Chart] = field(default_factory=MultiDict)
|
||||
global_timing: Optional[Timing] = None
|
Loading…
Reference in New Issue
Block a user