diff --git a/jubeatools/formats/malody/dump.py b/jubeatools/formats/malody/dump.py new file mode 100644 index 0000000..6e770b4 --- /dev/null +++ b/jubeatools/formats/malody/dump.py @@ -0,0 +1,108 @@ +from pathlib import Path +from typing import List +import json +import time + +from jubeatools import song +from jubeatools.formats.dump_tools import make_dumper_from_chart_file_dumper +from jubeatools.formats.filetypes import ChartFile + +from . import schema as malody + + +def dump_malody_song(song: song.Song, **kwargs: dict) -> List[ChartFile]: + res = [] + for dif, chart, timing in song.iter_charts_with_timing(): + malody_chart = dump_malody_chart(song.metadata, dif, chart, timing) + json_chart = malody.Chart.Schema().dump(malody_chart) + chart_bytes = json.dumps(json_chart).encode("utf-8") + res.append(ChartFile(chart_bytes, song, dif, chart)) + + return res + + +dump_malody = make_dumper_from_chart_file_dumper( + internal_dumper=dump_malody_song, file_name_template=Path("{difficulty:l}.mc") +) + + +def dump_malody_chart(metadata: song.Metadata, dif: str, chart: song.Chart, timing: song.Timing) -> malody.Chart: + meta = dump_metadata(metadata, dif) + time = dump_timing(timing) + notes = dump_notes(chart.notes) + if metadata.audio is not None: + notes += dump_bgm(metadata.audio, timing) + return malody.Chart(meta=meta, time=time, note=notes) + +def dump_metadata(metadata: song.Metadata, dif: str) -> malody.Metadata: + return malody.Metadata( + cover="", + creator="", + background=none_or(str, metadata.cover), + version=dif, + id=0, + mode=malody.Mode.PAD, + time=int(time.time()), + song=malody.SongInfo( + title=metadata.title, + artist=metadata.artist, + id=0, + ) + ) + +def dump_timing(timing: song.Timing) -> List[malody.BPMEvent]: + sorted_events = sorted(timing.events, key=lambda e: e.time) + return [dump_bpm_change(e) for e in sorted_events] + +def dump_bpm_change(b: song.BPMEvent) -> malody.BPMEvent: + return malody.BPMEvent( + beat=beats_to_tuple(b.time), + bpm=b.BPM, + ) + +def dump_notes(notes: List[Union[song.TapNote, song.LongNote]]) -> List[Union[malody.TapNote, malody.LongNote]]: + return [dump_note(n) for n in notes] + + +@singledispatch +def dump_note( + n: Union[song.TapNote, song.LongNote] +) -> Union[malody.TapNote, malody.LongNote]: + raise NotImplementedError(f"Unknown note type : {type(n)}") + + +@dump_note.register +def dump_tap_note(n: song.TapNote) -> malody.TapNote: + return malody.TapNote( + beat=beats_to_tuple(n.time), + index=n.position.index, + ) + +@dump_note.register +def dump_long_note(n: song.LongNote) -> malody.LongNote: + return malody.LongNote( + beat=beats_to_tuple(n.time) + index=n.position.index, + endbeat=beats_to_tuple(n.time + n.duration), + endindex=n.tail_tip.index, + ) + +def dump_bgm(audio: Path, timing: song.Timing) -> malody.Sound: + return malody.Sound( + beat=beats_to_tuple(song.BeatsTime(0)), + sound=str(audio), + vol=100, + offset=-int(timing.beat_zero_offset*1000), + type=malody.SoundType.BACKGROUND_MUSIC, + isBgm=None, + x=None, + ) + +def beats_to_tuple(b: song.BeatsTime) -> Tuple[int, int, int]: + integer_part = int(b) + remainder = b % 1 + return ( + integer_part, + remainder.numerator, + remainder.denominator, + ) \ No newline at end of file diff --git a/jubeatools/formats/malody/schema.py b/jubeatools/formats/malody/schema.py index 875a527..a48dd1d 100644 --- a/jubeatools/formats/malody/schema.py +++ b/jubeatools/formats/malody/schema.py @@ -27,7 +27,7 @@ class Metadata: version: Optional[str] # freeform difficulty name id: Optional[int] mode: int - time: int # creation timestamp ? + time: Optional[int] # creation timestamp ? song: SongInfo