From b28321f21a6486784503adfb72962702e6a77f22 Mon Sep 17 00:00:00 2001 From: donmai-me <71143298+donmai-me@users.noreply.github.com> Date: Sun, 14 Nov 2021 00:36:52 +0800 Subject: [PATCH] Push 0.14.0 --- CHANGELOG.md | 9 +-- maiconverter/__init__.py | 2 +- maiconverter/__main__.py | 2 +- maiconverter/{maiconverter.py => cli.py} | 40 ++++++------ maiconverter/event/note.py | 7 +++ maiconverter/maima2/ma2note.py | 5 +- maiconverter/maima2/maima2.py | 79 ++++++++++++++++++------ maiconverter/maisxt/maisxt.py | 15 +---- maiconverter/simai/simai.py | 54 +++++++++++----- setup.py | 2 +- 10 files changed, 140 insertions(+), 75 deletions(-) rename maiconverter/{maiconverter.py => cli.py} (95%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef40367..736eb81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.14.0] - 2021-11-14 ### Added - New time tracking functions: measure_to_second, second_to_measure, and quantise. +- Time functions measure_to_second and second_to_measure has an optional parameter `include_metronome_ticks`, set to True by default, that takes into account the first few metronome ticks at the start. - New script (sxt_change_bpm.py) that converts an MaiSxt chart written in one BPM to another. - New script (sxt_to_ma2_with_bpms.py) that converts an MaiSxt chart to a MaiMa2 chart that copies the BPM skeleton of another MaiMa2 chart. @@ -22,8 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - MaiMa2 resolution moved from being an attribute to a parameter in export method. - Migrated changelog format to keep a changelog and remove entries from unreleased versions. - MaiMa2 and MaiSxt notes no longer have a newline character at the end when converted to string. -- slide_distance and is_slide_cw moved from simai package to tool package. -- Time functions measure_to_second and second_to_measure now has an optional parameter `include_metronome_ticks`, which is set to True by default, that takes into account the first few metronome ticks at the start. +- slide_distance and is_slide_cw moved from simai package to tool package. ### Removed - Old scripts in the scripts folder. @@ -42,5 +42,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ma2 to Sdt conversion and vice versa. - Simai to Sdt conversion and vice versa. -[Unreleased]: https://github.com/donmai-me/MaiConverter/compare/0.13.0...HEAD +[Unreleased]: https://github.com/donmai-me/MaiConverter/compare/0.14.0...HEAD +[0.14.0]: https://github.com/donmai-me/MaiConverter/compare/0.13.0...0.14.0 [0.13.0]: https://github.com/donmai-me/MaiConverter/compare/0.12.0...0.13.0 diff --git a/maiconverter/__init__.py b/maiconverter/__init__.py index 6e558de..3968851 100644 --- a/maiconverter/__init__.py +++ b/maiconverter/__init__.py @@ -1,5 +1,5 @@ from importlib.metadata import version, PackageNotFoundError -from .maiconverter import main +from maiconverter.cli import main try: __version__ = version("maiconverter") diff --git a/maiconverter/__main__.py b/maiconverter/__main__.py index d25bf63..dc2ed7b 100644 --- a/maiconverter/__main__.py +++ b/maiconverter/__main__.py @@ -1,3 +1,3 @@ -from .maiconverter import main +from maiconverter.cli import main main() diff --git a/maiconverter/maiconverter.py b/maiconverter/cli.py similarity index 95% rename from maiconverter/maiconverter.py rename to maiconverter/cli.py index 188d9a5..3674f85 100644 --- a/maiconverter/maiconverter.py +++ b/maiconverter/cli.py @@ -51,7 +51,7 @@ def crypto(args, output): file for file in files if not re.search(r"\.tbl", file) is None ] else: - # Only accept ".sxt" ".sct" ".szt" ".srt" files + # Only accept ".sdt" ".sct" ".szt" ".srt" files files = [ file for file in files if not re.search(r"\.s.t", file) is None ] @@ -122,7 +122,7 @@ def handle_ma2(file, name, output_path, args): if args.command == "ma2tosdt": output = ma2_to_sdt(ma2, convert_touch=args.convert_touch) - ext = ".sxt" + ext = ".sdt" else: output = ma2_to_simai(ma2) ext = ".txt" @@ -166,7 +166,7 @@ def handle_simai_chart(file, name, output_path, args): simai.offset(args.delay) if args.command == "simaitosdt": - ext = ".sxt" + ext = ".sdt" converted = simai_to_sdt(simai, convert_touch=args.convert_touch) else: ext = ".ma2" @@ -175,7 +175,10 @@ def handle_simai_chart(file, name, output_path, args): with open( os.path.join(output_path, name + ext), "w+", newline="\r\n", encoding="utf-8" ) as out: - out.write(converted.export(resolution=args.resolution)) + if isinstance(converted, MaiSxt): + out.write(converted.export()) + else: + out.write(converted.export(resolution=args.resolution)) def handle_simai_file(file, output_path, args): @@ -187,7 +190,7 @@ def handle_simai_file(file, output_path, args): try: if args.command == "simaifiletosdt": - ext = ".sxt" + ext = ".sdt" converted = simai_to_sdt(simai_chart, convert_touch=args.convert_touch) else: ext = ".ma2" @@ -237,20 +240,21 @@ def handle_db(input_path, output_dir, command, key): f.write(output) -def parse_arg(): - COMMANDS = [ - "encrypt", - "decrypt", - "ma2tosdt", - "ma2tosimai", - "sdttoma2", - "sdttosimai", - "simaifiletoma2", - "simaifiletosdt", - "simaitoma2", - "simaitosdt", - ] +COMMANDS = [ + "encrypt", + "decrypt", + "ma2tosdt", + "ma2tosimai", + "sdttoma2", + "sdttosimai", + "simaifiletoma2", + "simaifiletosdt", + "simaitoma2", + "simaitosdt", +] + +def parse_arg(): parser = argparse.ArgumentParser( description="Tool for converting MaiMai chart formats", allow_abbrev=False, diff --git a/maiconverter/event/note.py b/maiconverter/event/note.py index 545e02f..d21e50e 100644 --- a/maiconverter/event/note.py +++ b/maiconverter/event/note.py @@ -1,6 +1,7 @@ import enum import math from functools import total_ordering +from typing import Dict from .event import Event, EventType @@ -36,6 +37,12 @@ class NoteType(enum.Enum): end_slide = 128 +NOTE_ORDERING: Dict[int, int] = { + 5: 0, + 4: 0, +} + + class Note(Event): def __init__(self, measure: float, position: int, note_type: NoteType) -> None: if position < 0: diff --git a/maiconverter/maima2/ma2note.py b/maiconverter/maima2/ma2note.py index 6622708..d14daa8 100644 --- a/maiconverter/maima2/ma2note.py +++ b/maiconverter/maima2/ma2note.py @@ -1,13 +1,12 @@ import math from typing import Tuple -from ..event import MaiNote, NoteType, Event, EventType +from maiconverter.event import MaiNote, NoteType, Event, EventType +from maiconverter.tool import slide_distance # Dictionary for a note type's representation in ma2 # Does not cover slide notes, BPM, and meter events. # Use slide_dict for slides instead. -from ..tool import slide_distance - note_dict = { "TAP": 1, "HLD": 2, diff --git a/maiconverter/maima2/maima2.py b/maiconverter/maima2/maima2.py index 658d810..2554bc1 100644 --- a/maiconverter/maima2/maima2.py +++ b/maiconverter/maima2/maima2.py @@ -15,8 +15,12 @@ check_slide, ) from .tools import parse_v1 -from ..event import NoteType -from ..tool import second_to_measure, measure_to_second, offset_arg_to_measure +from maiconverter.event import NoteType +from maiconverter.tool import ( + second_to_measure, + measure_to_second, + offset_arg_to_measure, +) # Latest chart version MA2_VERSION = "1.03.00" @@ -98,7 +102,7 @@ def open(cls, path: str, encoding: str = "utf-8") -> MaiMa2: return ma2 - def parse_line(self, line: str) -> None: + def parse_line(self, line: str) -> MaiMa2: # Ma2 notes are tab-separated so we make a list called values that contains all the info values = line.rstrip().split("\t") line_type = values[0] @@ -111,7 +115,9 @@ def parse_line(self, line: str) -> None: else: raise ValueError(f"Unknown Ma2 version: {self.version}") - def set_bpm(self, measure: float, bpm: float) -> None: + return self + + def set_bpm(self, measure: float, bpm: float) -> MaiMa2: """Sets the bpm at given measure. Note: @@ -133,6 +139,8 @@ def set_bpm(self, measure: float, bpm: float) -> None: self.del_bpm(measure) self.bpms.append(BPM(measure, bpm)) + return self + def get_bpm(self, measure: float) -> float: """Gets the bpm at given measure. @@ -150,6 +158,8 @@ def get_bpm(self, measure: float) -> float: In a chart, the initial bpm is 180 then changes to 250 in measure 12. + >>> ma2 = MaiMa2() + >>> ma2.set_bpm(0.0, 180.0).set_bpm(12, 250) >>> ma2.get_bpm(0) 180.0 >>> ma2.get_bpm(11.99) @@ -174,7 +184,7 @@ def get_bpm(self, measure: float) -> float: return previous_bpm - def del_bpm(self, measure: float): + def del_bpm(self, measure: float) -> MaiMa2: """Deletes the bpm at given measure. Note: @@ -186,6 +196,7 @@ def del_bpm(self, measure: float): Examples: Delete the BPM change defined at measure 24. + >>> ma2 = MaiMa2() >>> ma2.del_bpm(24) """ bpms = [ @@ -194,12 +205,14 @@ def del_bpm(self, measure: float): for x in bpms: self.bpms.remove(x) + return self + def set_meter( self, measure: float, meter_numerator: int, meter_denominator: int, - ) -> None: + ) -> MaiMa2: """Sets the meter signature at given measure. Note: @@ -222,6 +235,8 @@ def set_meter( self.del_meter(measure) self.meters.append(Meter(measure, meter_numerator, meter_denominator)) + return self + def get_meter(self, measure: float) -> Tuple[int, int]: """Gets the bpm at given measure. @@ -258,13 +273,15 @@ def get_meter(self, measure: float) -> Tuple[int, int]: return previous_meter.numerator, previous_meter.numerator - def del_meter(self, measure: float): + def del_meter(self, measure: float) -> MaiMa2: meters = [ x for x in self.meters if math.isclose(x.measure, measure, abs_tol=0.0001) ] for x in meters: self.meters.remove(x) + return self + def add_tap( self, measure: float, @@ -272,7 +289,7 @@ def add_tap( is_break: bool = False, is_star: bool = False, is_ex: bool = False, - ) -> None: + ) -> MaiMa2: """Adds a tap note to the list of notes. Used to add TAP, XTP, BRK, STR, or BST to the list of notes. Increments @@ -321,7 +338,9 @@ def add_tap( self.notes.append(tap_note) - def del_tap(self, measure: float, position: int) -> None: + return self + + def del_tap(self, measure: float, position: int) -> MaiMa2: """Deletes a tap note from the list of notes. Args: @@ -364,13 +383,15 @@ def del_tap(self, measure: float, position: int) -> None: elif not is_break and not is_star and not is_ex: self.notes_stat["TAP"] -= 1 + return self + def add_hold( self, measure: float, position: int, duration: float, is_ex: bool = False, - ) -> None: + ) -> MaiMa2: """Adds a hold note to the list of notes. Used to add HLD or XHO to the list of notes. Increments the total @@ -400,7 +421,9 @@ def add_hold( self.notes.append(hold_note) - def del_hold(self, measure: float, position: int) -> None: + return self + + def del_hold(self, measure: float, position: int) -> MaiMa2: """Deletes the matching hold note in the list of notes. If there are multiple matches, all matching notes are deleted. If there are no match, nothing happens. @@ -431,6 +454,8 @@ def del_hold(self, measure: float, position: int) -> None: else: self.notes_stat["HLD"] -= 1 + return self + def add_slide( self, measure: float, @@ -440,7 +465,7 @@ def add_slide( pattern: int, delay: float = 0.25, slide_check: bool = True, - ) -> None: + ) -> MaiMa2: """Adds a slide note to the list of notes. Used to add SI_, SCL, SCR, SUL, SUR, SSL, SSR, SV_, SXL, SXR, @@ -480,12 +505,14 @@ def add_slide( self.notes_stat["SLD"] += 1 self.notes.append(slide_note) + return self + def del_slide( self, measure: float, start_position: int, end_position: int, - ) -> None: + ) -> MaiMa2: slide_notes = [ x for x in self.notes @@ -499,6 +526,8 @@ def del_slide( self.notes.remove(note) self.notes_stat["SLD"] -= 1 + return self + def add_touch_tap( self, measure: float, @@ -506,7 +535,7 @@ def add_touch_tap( region: str, is_firework: bool = False, size: str = "M1", - ) -> None: + ) -> MaiMa2: """Adds a touch tap note to the list of notes. Used to add TTP to the list of notes. Increments the total touch taps @@ -530,7 +559,9 @@ def add_touch_tap( self.notes_stat["TTP"] += 1 self.notes.append(touch_tap) - def del_touch_tap(self, measure: float, position: int, region: str) -> None: + return self + + def del_touch_tap(self, measure: float, position: int, region: str) -> MaiMa2: touch_taps = [ x for x in self.notes @@ -543,6 +574,8 @@ def del_touch_tap(self, measure: float, position: int, region: str) -> None: self.notes.remove(note) self.notes_stat["TTP"] -= 1 + return self + def add_touch_hold( self, measure: float, @@ -551,7 +584,7 @@ def add_touch_hold( duration: float, is_firework: bool = False, size: str = "M1", - ) -> None: + ) -> MaiMa2: """Adds a touch hold note to the list of notes. Used to add THO to the list of notes. Increments the total touch holds @@ -579,12 +612,14 @@ def add_touch_hold( self.notes_stat["THO"] += 1 self.notes.append(touch_tap) + return self + def del_touch_hold( self, measure: float, position: int, region: str, - ) -> None: + ) -> MaiMa2: touch_holds = [ x for x in self.notes @@ -597,24 +632,28 @@ def del_touch_hold( self.notes.remove(note) self.notes_stat["THO"] -= 1 - def offset(self, offset: Union[float, str]) -> None: + return self + + def offset(self, offset: Union[float, str]) -> MaiMa2: offset = offset_arg_to_measure(offset, self.second_to_measure) for note in self.notes: note.measure = round(note.measure + offset, 4) for bpm in self.bpms: - if bpm.measure == 0: + if 0 <= bpm.measure <= 1: continue bpm.measure = round(bpm.measure + offset, 4) for meter in self.meters: - if meter.measure == 0: + if 0 <= meter.measure <= 1: continue meter.measure = round(meter.measure + offset, 4) + return self + def measure_to_second(self, measure: float) -> float: bpms = [(bpm.measure, bpm.bpm) for bpm in self.bpms] diff --git a/maiconverter/maisxt/maisxt.py b/maiconverter/maisxt/maisxt.py index 100d036..d467cc0 100644 --- a/maiconverter/maisxt/maisxt.py +++ b/maiconverter/maisxt/maisxt.py @@ -12,7 +12,7 @@ check_slide, ) from ..event import NoteType -from ..tool import measure_to_second, second_to_measure +from ..tool import measure_to_second, second_to_measure, offset_arg_to_measure class MaiSxt: @@ -435,18 +435,7 @@ def del_slide( return self def offset(self, offset: Union[float, str]) -> MaiSxt: - if isinstance(offset, float): - offset = offset - elif isinstance(offset, str) and offset[-1].lower() == "s": - offset = self.second_to_measure(float(offset[:-1])) - elif isinstance(offset, str) and "/" in offset: - fraction = offset.split("/") - if len(fraction) != 2: - raise ValueError(f"Invalid fraction: {offset}") - - offset = int(fraction[0]) / int(fraction[1]) - else: - offset = float(offset) + offset = offset_arg_to_measure(offset, self.second_to_measure) for note in self.notes: note.measure = round((note.measure + offset) * 10000.0) / 10000.0 diff --git a/maiconverter/simai/simai.py b/maiconverter/simai/simai.py index a8bd349..5793b34 100644 --- a/maiconverter/simai/simai.py +++ b/maiconverter/simai/simai.py @@ -229,7 +229,7 @@ def add_tap( is_break: bool = False, is_star: bool = False, is_ex: bool = False, - ) -> None: + ) -> SimaiChart: """Adds a tap note to the list of notes. Args: @@ -261,7 +261,9 @@ def add_tap( ) self.notes.append(tap_note) - def del_tap(self, measure: float, position: int) -> None: + return self + + def del_tap(self, measure: float, position: int) -> SimaiChart: """Deletes a tap note from the list of notes. Args: @@ -284,13 +286,15 @@ def del_tap(self, measure: float, position: int) -> None: for note in tap_notes: self.notes.remove(note) + return self + def add_hold( self, measure: float, position: int, duration: float, is_ex: bool = False, - ) -> None: + ) -> SimaiChart: """Adds a hold note to the list of notes. Args: @@ -311,7 +315,9 @@ def add_hold( hold_note = HoldNote(measure, position, duration, is_ex) self.notes.append(hold_note) - def del_hold(self, measure: float, position: int) -> None: + return self + + def del_hold(self, measure: float, position: int) -> SimaiChart: """Deletes the matching hold note in the list of notes. If there are multiple matches, all matching notes are deleted. If there are no match, nothing happens. @@ -337,6 +343,8 @@ def del_hold(self, measure: float, position: int) -> None: for note in hold_notes: self.notes.remove(note) + return self + def add_slide( self, measure: float, @@ -346,7 +354,7 @@ def add_slide( pattern: str, delay: float = 0.25, reflect_position: Optional[int] = None, - ) -> None: + ) -> SimaiChart: """Adds both a slide note to the list of notes. Args: @@ -381,12 +389,14 @@ def add_slide( ) self.notes.append(slide_note) + return self + def del_slide( self, measure: float, start_position: int, end_position: int, - ) -> None: + ) -> SimaiChart: slide_notes = [ x for x in self.notes @@ -398,22 +408,26 @@ def del_slide( for note in slide_notes: self.notes.remove(note) + return self + def add_touch_tap( self, measure: float, position: int, region: str, is_firework: bool = False, - ) -> None: + ) -> SimaiChart: touch_tap_note = TouchTapNote(measure, position, region, is_firework) self.notes.append(touch_tap_note) + return self + def del_touch_tap( self, measure: float, position: int, region: str, - ) -> None: + ) -> SimaiChart: touch_taps = [ x for x in self.notes @@ -425,6 +439,8 @@ def del_touch_tap( for note in touch_taps: self.notes.remove(note) + return self + def add_touch_hold( self, measure: float, @@ -432,18 +448,20 @@ def add_touch_hold( region: str, duration: float, is_firework: bool = False, - ) -> None: + ) -> SimaiChart: touch_hold_note = TouchHoldNote( measure, position, region, duration, is_firework ) self.notes.append(touch_hold_note) + return self + def del_touch_hold( self, measure: float, position: int, region: str, - ) -> None: + ) -> SimaiChart: touch_holds = [ x for x in self.notes @@ -455,7 +473,9 @@ def del_touch_hold( for note in touch_holds: self.notes.remove(note) - def set_bpm(self, measure: float, bpm: float) -> None: + return self + + def set_bpm(self, measure: float, bpm: float) -> SimaiChart: """Sets the bpm at given measure. Note: @@ -479,6 +499,8 @@ def set_bpm(self, measure: float, bpm: float) -> None: bpm_event = BPM(measure, bpm) self.bpms.append(bpm_event) + return self + def get_bpm(self, measure: float) -> float: """Gets the bpm at given measure. @@ -521,7 +543,7 @@ def get_bpm(self, measure: float) -> float: return previous_bpm - def del_bpm(self, measure: float): + def del_bpm(self, measure: float) -> SimaiChart: """Deletes the bpm at given measure. Note: @@ -542,18 +564,22 @@ def del_bpm(self, measure: float): for x in bpms: self.bpms.remove(x) - def offset(self, offset: Union[float, str]) -> None: + return self + + def offset(self, offset: Union[float, str]) -> SimaiChart: offset = offset_arg_to_measure(offset, self.second_to_measure) for note in self.notes: note.measure = round(note.measure + offset, 4) for bpm in self.bpms: - if bpm.measure == 0: + if 0 <= bpm.measure <= 1: continue bpm.measure = round(bpm.measure + offset, 4) + return self + def measure_to_second(self, measure: float) -> float: bpms = [(bpm.measure, bpm.bpm) for bpm in self.bpms] diff --git a/setup.py b/setup.py index 5bdb094..b84cf3f 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ ], package_data={"": ["*.lark"]}, entry_points={ - "console_scripts": ["maiconverter=maiconverter:main"], + "console_scripts": ["maiconverter=maiconverter.cli:main"], }, python_requires="~=3.8", use_scm_version=True,