From 889f8f96416b1ec5f062059353e3673f0d20eb5e Mon Sep 17 00:00:00 2001 From: EillesWan Date: Wed, 2 Apr 2025 02:52:41 +0800 Subject: [PATCH] =?UTF-8?q?Java=E7=89=88=EF=BC=81MSQ=E6=B5=81=E5=BC=8F?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=88=9D=E9=80=82=E9=85=8D=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- Musicreater/__init__.py | 8 +- Musicreater/constants.py | 2 +- Musicreater/exceptions.py | 30 +- Musicreater/experiment.py | 357 +++++++++++++++++++- Musicreater/magicmain.py | 2 +- Musicreater/main.py | 160 +++++++-- Musicreater/plugin/__init__.py | 2 +- Musicreater/plugin/addonpack/__init__.py | 2 +- Musicreater/plugin/addonpack/main.py | 2 +- Musicreater/plugin/archive.py | 2 +- Musicreater/plugin/bdx.py | 2 +- Musicreater/plugin/bdxfile/__init__.py | 2 +- Musicreater/plugin/bdxfile/main.py | 2 +- Musicreater/plugin/common.py | 2 +- Musicreater/plugin/main.py | 2 +- Musicreater/plugin/mcstructfile/__init__.py | 2 +- Musicreater/plugin/mcstructfile/main.py | 2 +- Musicreater/plugin/mcstructure.py | 2 +- Musicreater/plugin/noteblock.py | 2 +- Musicreater/plugin/schematic.py | 2 +- Musicreater/plugin/schematic/__init__.py | 2 +- Musicreater/plugin/schematic/main.py | 2 +- Musicreater/plugin/websocket/__init__.py | 2 +- Musicreater/plugin/websocket/main.py | 2 +- Musicreater/subclass.py | 2 +- Musicreater/types.py | 2 +- Musicreater/utils.py | 12 +- README.md | 1 + docs/FSQ文件格式.md | 3 + docs/MSQ文件格式.md | 69 +++- docs/转换乐器对照表.md | 2 +- example.py | 2 +- example_msq_opera.py | 14 + let_future_java.py | 141 ++++++++ requirements.txt | 1 + 36 files changed, 786 insertions(+), 61 deletions(-) create mode 100644 docs/FSQ文件格式.md create mode 100644 example_msq_opera.py create mode 100644 let_future_java.py diff --git a/.gitignore b/.gitignore index 9432ddf..447aee5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ # sth. can't open /msctPkgver/secrets/*.py /msctPkgver/secrets/*.c - +/fool/ # mystuff +/*.zip /.vscode /*.mid /*.midi diff --git a/Musicreater/__init__.py b/Musicreater/__init__.py index af7295f..60a2273 100644 --- a/Musicreater/__init__.py +++ b/Musicreater/__init__.py @@ -6,7 +6,7 @@ Musicreater(音·创) A free open source library used for dealing with **Minecraft** digital musics. 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵 The Licensor of Musicreater("this project") is Eilles Wan, bgArray. @@ -22,10 +22,10 @@ The Licensor of Musicreater("this project") is Eilles Wan, bgArray. # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__version__ = "2.2.2.2" -__vername__ = "接口小兼容" +__version__ = "2.2.3" +__vername__ = "Java版欲适配、初步MSQ流式适配" __author__ = ( - ("金羿", "Eilles Wan"), + ("金羿", "Eilles"), ("诸葛亮与八卦阵", "bgArray"), ("鱼旧梦", "ElapsingDreams"), ("偷吃不是Touch", "Touch"), diff --git a/Musicreater/constants.py b/Musicreater/constants.py index 280ac08..e331335 100644 --- a/Musicreater/constants.py +++ b/Musicreater/constants.py @@ -6,7 +6,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/exceptions.py b/Musicreater/exceptions.py index ebb5d2d..70965e9 100644 --- a/Musicreater/exceptions.py +++ b/Musicreater/exceptions.py @@ -6,7 +6,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory @@ -58,6 +58,7 @@ class MidiDestroyedError(MSCTBaseException): # super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args) # 此错误在本版本内已经不再使用 + class CommandFormatError(RuntimeError): """指令格式与目标格式不匹配而引起的错误""" @@ -75,6 +76,10 @@ class CommandFormatError(RuntimeError): # 这TM是什么错误? # 我什么时候写的这玩意? # 我哪知道这说的是啥? +# !!! +# 我知道这是什么了 —— 金羿 2025 0401 +# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息 +# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题 class NotDefineTempoError(MidiFormatException): @@ -109,7 +114,7 @@ class NoteOnOffMismatchError(MidiFormatException): super().__init__("音符不匹配", *args) -class ZeroSpeedError(ZeroDivisionError): +class ZeroSpeedError(MSCTBaseException, ZeroDivisionError): """以0作为播放速度的错误""" def __init__(self, *args): @@ -117,9 +122,26 @@ class ZeroSpeedError(ZeroDivisionError): super().__init__("播放速度为0", *args) -class IllegalMinimumVolumeError(ValueError): +class IllegalMinimumVolumeError(MSCTBaseException, ValueError): """最小播放音量有误的错误""" def __init__(self, *args): """最小播放音量错误""" - super().__init__("最小播放音量超出范围", *args) \ No newline at end of file + super().__init__("最小播放音量超出范围", *args) + + +class MusicSequenceDecodeError(MSCTBaseException): + """音乐序列解码错误""" + + def __init__(self, *args): + """音乐序列无法正确解码的错误""" + super().__init__("解码音符序列文件时出现问题", *args) + + +class MusicSequenceVerificationFailed(MusicSequenceDecodeError): + """音乐序列校验失败""" + + def __init__(self, *args): + """音符序列文件与其校验值不一致""" + super().__init__("音符序列文件校验失败", *args) + diff --git a/Musicreater/experiment.py b/Musicreater/experiment.py index e58e608..7640a90 100644 --- a/Musicreater/experiment.py +++ b/Musicreater/experiment.py @@ -6,7 +6,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory @@ -29,6 +29,359 @@ from .main import ( from .types import Tuple, List, Dict, ChannelType +class FutureMidiConvertJavaE(MidiConvert): + + def form_java_progress_bar( + self, + max_score: int, + scoreboard_name: str, + progressbar_style: ProgressBarStyle = DEFAULT_PROGRESSBAR_STYLE, + ) -> List[MineCommand]: + """ + 生成进度条 + + Parameters + ---------- + max_score: int + 最大的积分值 + scoreboard_name: str + 所使用的计分板名称 + progressbar_style: ProgressBarStyle + 此参数详见 ../docs/库的生成与功能文档.md#进度条自定义 + + Returns + ------- + list[MineCommand,] + """ + pgs_style = progressbar_style.base_style + """用于被替换的进度条原始样式""" + + """ + | 标识符 | 指定的可变量 | + |---------|----------------| + | `%%N` | 乐曲名(即传入的文件名)| + | `%%s` | 当前计分板值 | + | `%^s` | 计分板最大值 | + | `%%t` | 当前播放时间 | + | `%^t` | 曲目总时长 | + | `%%%` | 当前进度比率 | + | `_` | 用以表示进度条占位| + """ + perEach = max_score / pgs_style.count("_") + """每个进度条代表的分值""" + + result: List[MineCommand] = [] + + if r"%^s" in pgs_style: + pgs_style = pgs_style.replace(r"%^s", str(max_score)) + + if r"%^t" in pgs_style: + pgs_style = pgs_style.replace(r"%^t", mctick2timestr(max_score)) + + sbn_pc = scoreboard_name[:2] + if r"%%%" in pgs_style: + result.append( + MineCommand( + 'scoreboard objectives add {}PercT dummy "百分比计算"'.format( + sbn_pc + ), + annotation="新增临时百分比变量", + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players set MaxScore {} {}".format( + scoreboard_name, max_score + ), + annotation="设定音乐最大延迟分数", + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players set n100 {} 100".format(scoreboard_name), + annotation="设置常量100", + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[scores_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players operation @s {} = @s {}".format( + sbn_pc + "PercT", scoreboard_name + ), + annotation="赋值临时百分比", + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players operation @s {} *= n100 {}".format( + sbn_pc + "PercT", scoreboard_name + ), + annotation="转换临时百分比之单位至%(扩大精度)", + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players operation @s {} /= MaxScore {}".format( + sbn_pc + "PercT", scoreboard_name + ), + annotation="计算百分比", + ) + ) + + if r"%%t" in pgs_style: + result.append( + MineCommand( + 'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format( + sbn_pc + ), + annotation="新增临时分变量", + ) + ) + result.append( + MineCommand( + 'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format( + sbn_pc + ), + annotation="新增临时秒变量", + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players set n20 {} 20".format(scoreboard_name), + annotation="设置常量20", + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players set n60 {} 60".format(scoreboard_name), + annotation="设置常量60", + ) + ) + + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players operation @s {} = @s {}".format( + sbn_pc + "TMinT", scoreboard_name + ), + annotation="赋值临时分", + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players operation @s {} /= n20 {}".format( + sbn_pc + "TMinT", scoreboard_name + ), + annotation="转换临时分之单位为秒(缩减精度)", + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players operation @s {} = @s {}".format( + sbn_pc + "TSecT", sbn_pc + "TMinT" + ), + annotation="赋值临时秒", + ) + ) + + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players operation @s {} /= n60 {}".format( + sbn_pc + "TMinT", scoreboard_name + ), + annotation="转换临时分之单位为分(缩减精度)", + ) + ) + + result.append( + MineCommand( + self.execute_cmd_head.format( + "@a[score_" + scoreboard_name + "_min=1]" + ) + + "scoreboard players operation @s {} %= n60 {}".format( + sbn_pc + "TSecT", scoreboard_name + ), + annotation="确定临时秒(框定精度区间)", + ) + ) + + for i in range(pgs_style.count("_")): + npg_stl = ( + pgs_style.replace("_", progressbar_style.played_style, i + 1) + .replace("_", progressbar_style.to_play_style) + .replace(r"%%N", self.music_name) + .replace( + r"%%s", + '"},{"score":{"name":"*","objective":"' + + scoreboard_name + + '"}},{"text":"', + ) + .replace( + r"%%%", + r'"},{"score":{"name":"*","objective":"' + + sbn_pc + + r'PercT"}},{"text":"%', + ) + .replace( + r"%%t", + r'"},{"score":{"name":"*","objective":"{-}TMinT"}},{"text":":"},' + r'{"score":{"name":"*","objective":"{-}TSecT"}},{"text":"'.replace( + r"{-}", sbn_pc + ), + ) + ) + result.append( + MineCommand( + self.execute_cmd_head.format( + f"@a[score_{scoreboard_name}_min={int(i * perEach)},score_{scoreboard_name}={math.ceil((i + 1) * perEach)}]" + ) + + r'titleraw @s actionbar {"rawtext":[{"text":"' + + npg_stl + + r'"}]}', + annotation="进度条显示", + ) + ) + + if r"%%%" in pgs_style: + result.append( + MineCommand( + "scoreboard objectives remove {}PercT".format(sbn_pc), + annotation="移除临时百分比变量", + ) + ) + if r"%%t" in pgs_style: + result.append( + MineCommand( + "scoreboard objectives remove {}TMinT".format(sbn_pc), + annotation="移除临时分变量", + ) + ) + result.append( + MineCommand( + "scoreboard objectives remove {}TSecT".format(sbn_pc), + annotation="移除临时秒变量", + ) + ) + + self.progress_bar_command = result + return result + + def to_command_list_in_java_score( + self, + scoreboard_name: str = "mscplay", + source_of_sound: str = "ambient", + ) -> Tuple[List[List[MineCommand]], int, int]: + """ + 将midi转换为 Java 1.12.2 版我的世界命令列表 + + Parameters + ---------- + scoreboard_name: str + 我的世界的计分板名称 + + Returns + ------- + tuple( list[list[MineCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 ) + """ + + command_channels = [] + command_amount = 0 + max_score = 0 + + # 此处 我们把通道视为音轨 + for channel in self.channels.values(): + # 如果当前通道为空 则跳过 + if not channel: + continue + + this_channel = [] + + for note in channel: + max_score = max(max_score, note.start_tick) + + ( + mc_sound_ID, + relative_coordinates, + volume_percentage, + mc_pitch, + ) = minenote_to_command_paramaters( + note, + pitch_deviation=self.music_deviation, + ) + + this_channel.append( + MineCommand( + ( + self.execute_cmd_head.format( + "@a[score_{0}_min={1},score_{0}={1}]".format( + scoreboard_name, note.start_tick + ) + .replace("(", r"{") + .replace(")", r"}") + ) + + "playsound minecraft:block.{} {} @s ~{} ~{} ~{} {} {} {}".format( + mc_sound_ID, + source_of_sound, + *relative_coordinates, + volume_percentage, + 1.0 if note.percussive else mc_pitch, + self.minimum_volume, + ) + ), + annotation=( + "在{}播放噪音{}".format( + mctick2timestr(note.start_tick), + mc_sound_ID, + ) + if note.percussive + else "在{}播放乐音{}".format( + mctick2timestr(note.start_tick), + "{}:{:.2f}".format(mc_sound_ID, mc_pitch), + ) + ), + ), + ) + + command_amount += 1 + + if this_channel: + self.music_command_list.extend(this_channel) + command_channels.append(this_channel) + + return command_channels, command_amount, max_score + + class FutureMidiConvertRSNB(MidiConvert): """ 加入红石音乐适配 @@ -217,7 +570,7 @@ class FutureMidiConvertM5(MidiConvert): if not track: continue - note_queue = empty_midi_channels(staff=[]) + note_queue = empty_midi_channels(default_staff=[]) for msg in track: if msg.time != 0: diff --git a/Musicreater/magicmain.py b/Musicreater/magicmain.py index 35b552d..7bf67b9 100644 --- a/Musicreater/magicmain.py +++ b/Musicreater/magicmain.py @@ -23,7 +23,7 @@ Musicreater (音·创) A free open source library used for convert midi file into formats that is suitable for **Minecraft**. 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 ../License.md Terms & Conditions: ../License.md diff --git a/Musicreater/main.py b/Musicreater/main.py index 728860e..a13ec96 100644 --- a/Musicreater/main.py +++ b/Musicreater/main.py @@ -8,7 +8,7 @@ Musicreater (音·创) A free open source library used for dealing with **Minecraft** digital musics. 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵 The Licensor of Musicreater("this project") is Eilles Wan, bgArray. @@ -30,10 +30,11 @@ The Licensor of Musicreater("this project") is Eilles Wan, bgArray. # 赶快呼叫 程序员!Let's Go! 直ぐに呼びましょプログラマ レッツゴー! Hurry to call the programmer! Let's Go! -import math import os +import math import mido +from xxhash import xxh3_64, xxh3_128 from .constants import * from .exceptions import * @@ -203,6 +204,7 @@ class MusicSequence: ) = cls.to_music_note_channels( midi=mido_file, speed=speed_multiplier, + default_program_value=-1, # TODO 默认音色可调 pitched_note_rtable=pitched_note_referance_table, percussion_note_rtable=percussion_note_referance_table, default_tempo_value=default_tempo, @@ -226,6 +228,7 @@ class MusicSequence: def load_decode( cls, bytes_buffer_in: bytes, + verify: bool = True, ): """从字节码导入音乐序列""" @@ -238,8 +241,16 @@ class MusicSequence: music_name_ = bytes_buffer_in[8 : (stt_index := 8 + (group_1 >> 10))].decode( "GB18030" ) - channels_: MineNoteChannelType = empty_midi_channels(staff=[]) + channels_: MineNoteChannelType = empty_midi_channels(default_staff=[]) + total_note_count = 0 + if verify: + _header_index = stt_index + _total_verify_code = 0 + for channel_index in channels_.keys(): + channel_note_count = 0 + _channel_start_index = stt_index + for i in range( int.from_bytes( bytes_buffer_in[stt_index : (stt_index := stt_index + 4)], "big" @@ -258,10 +269,70 @@ class MusicSequence: is_high_time_precision=high_quantity, ) ) + channel_note_count += 1 stt_index = end_index - except: + except Exception as _err: print(channels_) - raise + raise MusicSequenceDecodeError(_err) + if verify: + if ( + _count_verify := xxh3_64( + channel_note_count.to_bytes(4, "big", signed=False), + seed=3, + ) + ).digest() != ( + _original_code := bytes_buffer_in[stt_index : stt_index + 8] + ): + raise MusicSequenceVerificationFailed( + "通道 {} 音符数量校验失败:{} -> `{}`;原始为 `{}`".format( + channel_index, + channel_note_count, + _count_verify.digest(), + _original_code, + ) + ) + if ( + _channel_verify := xxh3_64( + bytes_buffer_in[_channel_start_index:stt_index], + seed=channel_note_count, + ) + ).digest() != ( + _original_code := bytes_buffer_in[stt_index + 8 : stt_index + 16] + ): + raise MusicSequenceVerificationFailed( + "通道 {} 音符数据校验失败:`{}`;原始为 `{}`".format( + channel_index, + _channel_verify.digest(), + _original_code, + ) + ) + _total_verify_code ^= ( + _count_verify.intdigest() ^ _channel_verify.intdigest() + ) + total_note_count += channel_note_count + stt_index += 16 + + if verify: + if ( + _total_verify_res := xxh3_128( + _total_verify := ( + xxh3_64( + bytes_buffer_in[0:_header_index], + seed=total_note_count, + ).intdigest() + ^ _total_verify_code + ).to_bytes(8, "big"), + seed=total_note_count, + ).digest() + ) != (_original_code := bytes_buffer_in[stt_index:]): + raise MusicSequenceVerificationFailed( + "全曲最终校验失败。全曲音符数:{},全曲校验码异或和:`{}` -> `{}`;原始为 `{}`".format( + total_note_count, + _total_verify, + _total_verify_res, + _original_code, + ) + ) return cls( name_of_music=music_name_, @@ -280,6 +351,7 @@ class MusicSequence: ) -> bytes: """将音乐序列转为二进制字节码""" + # (已废弃) # 第一版的码头: MSQ# 字串编码: UTF-8 # 第一版格式 # 音乐名称长度 6 位 支持到 63 @@ -306,8 +378,11 @@ class MusicSequence: # for note_ in note_list: # bytes_buffer += note_.encode() + # (已废弃) # 第二版的码头: MSQ@ 字串编码: GB18030 + # 第三版的码头: MSQ! 字串编码: GB18030 大端字节序 + # 音乐名称长度 6 位 支持到 63 # 最小音量 minimum_volume 10 位 最大支持 1023 即三位小数 # 共 16 位 合 2 字节 @@ -316,33 +391,73 @@ class MusicSequence: # 总音调偏移 music_deviation 15 位 最大支持 -16383 ~ 16383 即 三位小数 # 共 16 位 合 2 字节 # +++ - # 音乐名称 music_name 长度最多63 支持到 31 个中文字符 或 63 个西文字符 + # 音乐名称 music_name 长度最多 63 支持到 31 个中文字符 或 63 个西文字符 bytes_buffer = ( - b"MSQ@" + b"MSQ!" + ( - (len(r := self.music_name.encode("GB18030")) << 10) # 音乐名称长度 - + round(self.minimum_volume * 1000) # 最小音量 + (len(r := self.music_name.encode("GB18030")) << 10) # 音乐名称长度 + + round(self.minimum_volume * 1000) # 最小音量 ).to_bytes(2, "big") + ( ( ( (high_time_precision << 1) # 是否启用“高精度”音符时间控制 - + (1 if (k := round(self.music_deviation * 1000)) < 0 else 0) # 总音调偏移的正负位 + + ( + 1 if (k := round(self.music_deviation * 1000)) < 0 else 0 + ) # 总音调偏移的正负位 ) << 14 ) - + abs(k) # 总音调偏移 + + abs(k) # 总音调偏移 ).to_bytes(2, "big", signed=False) + r ) + # 此上是音符序列的元信息,接下来是多通道的音符序列 + + # 每个通道的开头是 32 位的 序列长度 共 4 字节 + # 接下来根据这个序列的长度来读取音符数据 + # 若启用“高精度”,则每个音符皆添加一个字节,用于存储音符时间控制精度偏移 # 此值每增加 1,则音符向后播放时长增加 1/1250 秒 + # 高精度功能在 MineNote 类实现 + + # (第三版新增)每个通道结尾包含一个 128 位的 XXHASH 校验值,用以标识该通道结束 + # 在这 128 位里,前 64 位是该通道音符数的 XXHASH64 校验值,以 3 作为种子值 + # 后 64 位是整个通道全部字节串的 XXHASH64 校验值(包括通道开头的音符数),以 该通道音符数 作为种子值 + _final_hash_codec = xxh3_64( + bytes_buffer, seed=self.total_note_count + ).intdigest() + for channel_index, note_list in self.channels.items(): - bytes_buffer += len(note_list).to_bytes(4, "big") + channel_buffer = len_buffer = len(note_list).to_bytes( + 4, "big", signed=False + ) for note_ in note_list: - bytes_buffer += note_.encode(is_high_time_precision=high_time_precision) + channel_buffer += note_.encode( + is_high_time_precision=high_time_precision + ) + _now_hash_codec_spliter = xxh3_64(len_buffer, seed=3) + _now_hash_codec_verifier = xxh3_64( + channel_buffer, seed=int.from_bytes(len_buffer, "big", signed=False) + ) + + bytes_buffer += channel_buffer + bytes_buffer += ( + _now_hash_codec_spliter.digest() + _now_hash_codec_verifier.digest() + ) + + _final_hash_codec ^= ( + _now_hash_codec_spliter.intdigest() + ^ _now_hash_codec_verifier.intdigest() + ) + + # 在所有音符通道表示完毕之后,由一个 128 位的 XXHASH 校验值,用以标识文件结束并校验 + # 该 128 位的校验值是对于前述所有校验值的异或所得值之 XXHASH128 校验值,以 全曲音符总数 作为种子值 + bytes_buffer += xxh3_128( + _final_hash_codec.to_bytes(8, "big"), seed=self.total_note_count + ).digest() return bytes_buffer @@ -378,6 +493,7 @@ class MusicSequence: midi: mido.MidiFile, ignore_mismatch_error: bool = True, speed: float = 1.0, + default_program_value: int = -1, default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO, pitched_note_rtable: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE, percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE, @@ -392,8 +508,10 @@ class MusicSequence: 需要处理的midi对象 speed: float 音乐播放速度倍数 + default_program_value: int + 默认的 MIDI 乐器值 default_tempo_value: int - 默认的MIDI TEMPO值 + 默认的 MIDI TEMPO 值 pitched_note_rtable: Dict[int, Tuple[str, int]] 乐音乐器Midi-MC对照表 percussion_note_rtable: Dict[int, Tuple[str, int]] @@ -411,8 +529,10 @@ class MusicSequence: raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。") # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 - midi_channels: MineNoteChannelType = empty_midi_channels(staff=[]) - channel_program: Dict[int, int] = empty_midi_channels(staff=-1) + midi_channels: MineNoteChannelType = empty_midi_channels(default_staff=[]) + channel_program: Dict[int, int] = empty_midi_channels( + default_staff=default_program_value + ) tempo = default_tempo_value note_count = 0 note_count_per_instrument: Dict[str, int] = {} @@ -426,7 +546,7 @@ class MusicSequence: int, ] ], - ] = empty_midi_channels(staff=[]) + ] = empty_midi_channels(default_staff=[]) note_queue_B: Dict[ int, List[ @@ -435,7 +555,7 @@ class MusicSequence: int, ] ], - ] = empty_midi_channels(staff=[]) + ] = empty_midi_channels(default_staff=[]) # 直接使用mido.midifiles.tracks.merge_tracks转为单轨 # 采用的时遍历信息思路 @@ -957,7 +1077,9 @@ class MidiConvert(MusicSequence): self.progress_bar_command = result return result - def redefine_execute_format(self, is_old_exe_cmd_using: bool = False): + def redefine_execute_format( + self, is_old_exe_cmd_using: bool = False + ) -> "MidiConvert": """ 根据是否使用旧版执行命令格式,重新定义执行命令的起始格式。 diff --git a/Musicreater/plugin/__init__.py b/Musicreater/plugin/__init__.py index 851d10b..c50758a 100644 --- a/Musicreater/plugin/__init__.py +++ b/Musicreater/plugin/__init__.py @@ -3,7 +3,7 @@ 存放非音·创本体的附加功能件 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/addonpack/__init__.py b/Musicreater/plugin/addonpack/__init__.py index 1bea77f..28b92af 100644 --- a/Musicreater/plugin/addonpack/__init__.py +++ b/Musicreater/plugin/addonpack/__init__.py @@ -3,7 +3,7 @@ 用以生成附加包的附加功能 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/addonpack/main.py b/Musicreater/plugin/addonpack/main.py index be8a80f..4c96fed 100644 --- a/Musicreater/plugin/addonpack/main.py +++ b/Musicreater/plugin/addonpack/main.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/archive.py b/Musicreater/plugin/archive.py index 2e4d503..3762659 100644 --- a/Musicreater/plugin/archive.py +++ b/Musicreater/plugin/archive.py @@ -6,7 +6,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/bdx.py b/Musicreater/plugin/bdx.py index 2f15939..3edb4ec 100644 --- a/Musicreater/plugin/bdx.py +++ b/Musicreater/plugin/bdx.py @@ -5,7 +5,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/bdxfile/__init__.py b/Musicreater/plugin/bdxfile/__init__.py index 5a87f23..3ff2cd1 100644 --- a/Musicreater/plugin/bdxfile/__init__.py +++ b/Musicreater/plugin/bdxfile/__init__.py @@ -3,7 +3,7 @@ 用以生成BDX结构文件的附加功能 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/bdxfile/main.py b/Musicreater/plugin/bdxfile/main.py index 3f85364..30b24a0 100644 --- a/Musicreater/plugin/bdxfile/main.py +++ b/Musicreater/plugin/bdxfile/main.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/common.py b/Musicreater/plugin/common.py index 2bda08b..019ab5e 100644 --- a/Musicreater/plugin/common.py +++ b/Musicreater/plugin/common.py @@ -5,7 +5,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/main.py b/Musicreater/plugin/main.py index e34a9af..045cb56 100644 --- a/Musicreater/plugin/main.py +++ b/Musicreater/plugin/main.py @@ -5,7 +5,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/mcstructfile/__init__.py b/Musicreater/plugin/mcstructfile/__init__.py index d1e35e3..8fba567 100644 --- a/Musicreater/plugin/mcstructfile/__init__.py +++ b/Musicreater/plugin/mcstructfile/__init__.py @@ -3,7 +3,7 @@ 用以生成单个mcstructure文件的附加功能 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/mcstructfile/main.py b/Musicreater/plugin/mcstructfile/main.py index 6d2c978..72db293 100644 --- a/Musicreater/plugin/mcstructfile/main.py +++ b/Musicreater/plugin/mcstructfile/main.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/mcstructure.py b/Musicreater/plugin/mcstructure.py index cb36397..5830471 100644 --- a/Musicreater/plugin/mcstructure.py +++ b/Musicreater/plugin/mcstructure.py @@ -5,7 +5,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/noteblock.py b/Musicreater/plugin/noteblock.py index 548e924..47d9d5c 100644 --- a/Musicreater/plugin/noteblock.py +++ b/Musicreater/plugin/noteblock.py @@ -5,7 +5,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/schematic.py b/Musicreater/plugin/schematic.py index 69aaa24..6374ab7 100644 --- a/Musicreater/plugin/schematic.py +++ b/Musicreater/plugin/schematic.py @@ -5,7 +5,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/schematic/__init__.py b/Musicreater/plugin/schematic/__init__.py index 9fe2123..288e27e 100644 --- a/Musicreater/plugin/schematic/__init__.py +++ b/Musicreater/plugin/schematic/__init__.py @@ -3,7 +3,7 @@ 用以生成Schematic结构的附加功能 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/schematic/main.py b/Musicreater/plugin/schematic/main.py index 2daad1c..7f6b5e2 100644 --- a/Musicreater/plugin/schematic/main.py +++ b/Musicreater/plugin/schematic/main.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/websocket/__init__.py b/Musicreater/plugin/websocket/__init__.py index 85469b2..091b880 100644 --- a/Musicreater/plugin/websocket/__init__.py +++ b/Musicreater/plugin/websocket/__init__.py @@ -3,7 +3,7 @@ 用以启动WebSocket服务器播放的附加功能 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/plugin/websocket/main.py b/Musicreater/plugin/websocket/main.py index 78f52c9..b169311 100644 --- a/Musicreater/plugin/websocket/main.py +++ b/Musicreater/plugin/websocket/main.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/subclass.py b/Musicreater/subclass.py index fee16bf..66636ca 100644 --- a/Musicreater/subclass.py +++ b/Musicreater/subclass.py @@ -6,7 +6,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/types.py b/Musicreater/types.py index cb11fef..6f18b88 100644 --- a/Musicreater/types.py +++ b/Musicreater/types.py @@ -6,7 +6,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/utils.py b/Musicreater/utils.py index 01688b4..d2eb470 100644 --- a/Musicreater/utils.py +++ b/Musicreater/utils.py @@ -5,7 +5,7 @@ """ 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory @@ -38,7 +38,9 @@ from .types import ( ) -def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]: +def empty_midi_channels( + channel_count: int = 17, default_staff: Any = {} +) -> Dict[int, Any]: """ 空MIDI通道字典 """ @@ -46,7 +48,11 @@ def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, A return dict( ( i, - (staff.copy() if isinstance(staff, (dict, list)) else staff), + ( + default_staff.copy() + if isinstance(default_staff, (dict, list)) + else default_staff + ), ) # 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了 for i in range(channel_count) ) diff --git a/README.md b/README.md index f8b7e79..8d9eb69 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ - 感谢 **小埋**\ 反馈附加包生成时缺少描述和标题的问题。 -
感谢 **油炸**<QQ2836146704> 激励我们不断开发新的内容。
- 感谢 **雨**\ 反馈在新版本的指令格式下,计分板播放器的附加包无法播放的问题。 +- 感谢 **梦幻duang**\ 为我们提供 Java 1.12.2 版本命令格式参考。 > 感谢广大群友为此库提供的测试和建议等 > diff --git a/docs/FSQ文件格式.md b/docs/FSQ文件格式.md new file mode 100644 index 0000000..92212d3 --- /dev/null +++ b/docs/FSQ文件格式.md @@ -0,0 +1,3 @@ +# FSQ 文件格式 + +还在设计 \ No newline at end of file diff --git a/docs/MSQ文件格式.md b/docs/MSQ文件格式.md index b2b2e01..8dfc0c5 100644 --- a/docs/MSQ文件格式.md +++ b/docs/MSQ文件格式.md @@ -2,18 +2,25 @@ MSQ 文件是 音·创 存储音符序列的一种格式,取自 MusicSeQuence。 -## MSQ 第二版 +现在 音·创 及其上游软件使用的是在 第二版 的基础上增设验证功能的 MSQ 第三版。 -第二版的码头是 MSQ@ ,这一版中,所有的**字符串**以 _**GB18030**_ 编码进行编解码,**数值**以**_大端序_**存储。 +## MSQ 第三版 -码头是文件前四个字节的内容,这一部分内容是可读的 ASCII 字串。因此,第二版的文件前四个字节的内容必为 MSQ@。 +第二版的码头是 `MSQ@` ,这一版中,所有的**字符串**以 _**GB18030**_ 编码进行编解码,**数值**以**_大端序_**存储。 + +MSQ 第三版的码头是 `MSQ!`。 + +码头是文件前四个字节的内容,这一部分内容是可读的 ASCII 字串。因此,第三版的文件前四个字节的内容必为 `MSQ!`。 取 MSQ@ 是因为美式键盘上 @ 是 Shift+2 键 按下取得的,故代表 MSQ 第二版。 +你猜为什么第三版是 `MSQ!`。 + ### 元信息 | 信息名称 | 西文代号 | 位长(多少个 0 或 1) | 支持说明 | | ------------------------------ | -------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **码头** | _无_ | 32 位 | 值为 `MSQ!` | | **音乐名称长度** | music_name_length | 6 位 | 支持数值 0~63 | | **最小音量** | minimum_volume | 10 位 | 支持数值 0~1023,注意,这里每个 1 代表最小音量的 0.001 个单位,即取值是此处表示数字的千分倍 | | **是否启用高精度音符时间控制** | enable_high_precision_time | 1 位 | 1 是启用,反之同理 | @@ -39,4 +46,58 @@ MSQ 文件是 音·创 存储音符序列的一种格式,取自 MusicSeQuence | **是否启用声像位移** | is_displacement_included | 1 位 | 1 是启用,反之同理 | | **时间精度提升值**(非必含) | high_time_precision | 8 位 | 支持数值 0~255,若在 元信息 中启用**高精度音符时间控制**,则此值启用,代表音符时间控制精度偏移,此值每增加 1,则音符开始时刻向后增加 1/1250 秒 | | **乐器名称** | sound_name | 依据先前定义 | 最多可支持 31 个中文字符 或 63 个西文字符,其长度取决于先前获知的 “乐器名称长度” 的定义 | -| **声像位移**(非必含) | position_displacement | 共三个值,每个值 16 位 共 48 位 | 若前述**是否启用声像位移**已启用,则此值启用;三个值分别代表 x、y、z 轴上的便宜,每个值支持数值 0~65535,注意,这里每个 1 代表最小音量的 0.001 个单位,即取值是此处表示数字的千分倍 | +| **声像位移**(非必含) | position_displacement | 共三个值,每个值 16 位 共 48 位 | 若前述**是否启用声像位移**已启用,则此值启用;三个值分别代表 x、y、z 轴上的偏移,每个值支持数值 0~65535,注意,这里每个 1 代表最小音量的 0.001 个单位,即取值是此处表示数字的千分倍 | + +### 序列验证 + +_第三版新增_ + +在每个音符序列结尾包含一个 128 位的校验值,用以标识该序列结束的同时,验证该序列的完整性。 +在这 128 位里,前 64 位是该通道音符数的 XXHASH64 校验值,以 3 作为种子值。 +后 64 位是整个通道全部字节串的 XXHASH64 校验值(包括通道开头的音符数),以 该通道音符数 作为种子值。 + +### 文件验证 + +_第三版新增_ + +在所有有效数据之后,包含一个 128 位的校验值,用以标识整个文件结束的同时,验证整个文件的完整性。 + +该 128 位的校验值是 包括码头在内的元信息的 XXHASH64 校验值(种子值是全曲音符数) 对于前述所有校验值彼此异或的异或 所得值之 XXHASH128 校验值,以 全曲音符总数 作为种子值。 + +请注意,是前述每个 XXHASH64 校验值的异或(每次取 XXHASH64 都计一遍),也就并非是每个序列结尾,那个已经合并了的 128 位校验值再彼此异或。对于这个异或值,再取其种子是 全曲音符数 的 XXHASH128 校验字节码。 + +听起来很复杂?我来举个例子。以下是该算法的伪代码。我们设: + +- `meta_info` : `bytes` 为 元信息字节串 +- `note_seq_1` : `bytes` 为 第一个音符序列的编码字节串 +- `note_seq_2` : `bytes` 为 第二个音符序列的编码字节串 +- `XXH64(bytes, seed)` : `bytes` 为 XXHASH64 校验函数 +- `XXH128(bytes, seed)` : `bytes` 为 XXHASH128 校验函数 +- `XOR(bytesLike, bytesLike)` : `bytes` 为 异或 函数 +- `note_count` : `int` 为 全曲音符数 +- `seq_1_note_count` : `int` 为 第一个音符序列的音符数 +- `seq_2_note_count` : `int` 为 第二个音符序列的音符数 + +为了简化,我们假设只有两个序列,实际上每个通道都是一个序列(最多 16 个序列) + +那么,一个完整的 MSQ 文件应当如下排列其字节串: + +```assembly +ADD meta_info +ADD note_seq_1 +ADD XXH64(seq_1_note_count, 3) +ADD XXH64(note_seq_1, seq_1_note_count) +ADD note_seq_2 +ADD XXH64(seq_2_note_count, 3) +ADD XXH64(note_seq_2, seq_2_note_count) +ADD XXH128( + XOR( + XXH64(meta_info, note_count), + XOR( + XXH64(seq_1_note_count, 3), + XXH64(note_seq_1, seq_1_note_count) + ), + ), + note_count + ) +``` diff --git a/docs/转换乐器对照表.md b/docs/转换乐器对照表.md index 46b7caf..d6f747c 100644 --- a/docs/转换乐器对照表.md +++ b/docs/转换乐器对照表.md @@ -13,7 +13,7 @@ **_使用时请遵循协议规定_** - 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -- Copyright © 2024 EillesWan & bgArray +- Copyright © 2025 Eilles & bgArray * 开源相关声明请见 仓库根目录下的 License.md * Terms & Conditions: License.md in the root directory diff --git a/example.py b/example.py index d6e3c6f..417623c 100644 --- a/example.py +++ b/example.py @@ -10,7 +10,7 @@ Musicreater (音·创) A free open source library used for convert midi file into formats that is suitable for **Minecraft**. 版权所有 © 2024 金羿 & 诸葛亮与八卦阵 -Copyright © 2024 EillesWan & bgArray +Copyright © 2025 Eilles & bgArray 开源相关声明请见 ./License.md Terms & Conditions: ./License.md diff --git a/example_msq_opera.py b/example_msq_opera.py new file mode 100644 index 0000000..124ae8d --- /dev/null +++ b/example_msq_opera.py @@ -0,0 +1,14 @@ +import Musicreater + +msc_seq = Musicreater.MusicSequence.from_mido( + Musicreater.mido.MidiFile("./resources/测试片段.mid",), + "TEST-测试片段", +) + +with open("test.msq","wb") as f: + f.write(msq_bytes := msc_seq.encode_dump()) + +with open("test.msq","rb") as f: + msc_seq_r = Musicreater.MusicSequence.load_decode(f.read()) + +print(msc_seq_r) \ No newline at end of file diff --git a/let_future_java.py b/let_future_java.py new file mode 100644 index 0000000..45e5e42 --- /dev/null +++ b/let_future_java.py @@ -0,0 +1,141 @@ +""" +版权所有 © 2024 金羿 & 诸葛亮与八卦阵 +Copyright © 2025 Eilles & bgArray + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + + + + + +import os +import shutil + +from typing import Optional, Tuple + + +import Musicreater.experiment +from Musicreater.plugin.archive import compress_zipfile + + +def to_zip_pack_in_score( + midi_cvt: Musicreater.experiment.FutureMidiConvertJavaE, + dist_path: str, + progressbar_style: Optional[Musicreater.experiment.ProgressBarStyle], + scoreboard_name: str = "mscplay", + sound_source: str = "ambient", + auto_reset: bool = False, +) -> Tuple[int, int]: + """ + 将midi以计分播放器形式转换为我的世界函数附加包 + + Parameters + ---------- + midi_cvt: MidiConvert 对象 + 用于转换的MidiConvert对象 + dist_path: str + 转换结果输出的目标路径 + progressbar_style: ProgressBarStyle 对象 + 进度条对象 + scoreboard_name: str + 我的世界的计分板名称 + auto_reset: bool + 是否自动重置计分板 + + Returns + ------- + tuple[int指令数量, int音乐总延迟] + """ + + cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_java_score( + scoreboard_name=scoreboard_name, + source_of_sound=sound_source, + ) + + # 当文件f夹{self.outputPath}/temp/mscplyfuncs存在时清空其下所有项目,然后创建 + if os.path.exists(f"{dist_path}/temp/mscplyfuncs/"): + shutil.rmtree(f"{dist_path}/temp/mscplyfuncs/") + os.makedirs(f"{dist_path}/temp/mscplyfuncs/mscplay") + + # 写入stop.mcfunction + with open( + f"{dist_path}/temp/mscplyfuncs/stop.mcfunction", "w", encoding="utf-8" + ) as f: + f.write("scoreboard players reset @a {}".format(scoreboard_name)) + + # 将命令列表写入文件 + index_file = open( + f"{dist_path}/temp/mscplyfuncs/index.mcfunction", "w", encoding="utf-8" + ) + for i in range(len(cmdlist)): + index_file.write(f"function mscplyfuncs:mscplay/track{i + 1}\n") + with open( + f"{dist_path}/temp/mscplyfuncs/mscplay/track{i + 1}.mcfunction", + "w", + encoding="utf-8", + ) as f: + f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]])) + index_file.writelines( + ( + "scoreboard players add @a[score_{0}_min=1] {0} 1\n".format( + scoreboard_name + ), + ( + "scoreboard players reset @a[score_{0}_min={1}] {0}\n".format( + scoreboard_name, maxscore + 20 + ) + if auto_reset + else "" + ), + f"function mscplyfuncs:mscplay/progressShow\n" if progressbar_style else "", + ) + ) + + if progressbar_style: + with open( + f"{dist_path}/temp/mscplyfuncs/mscplay/progressShow.mcfunction", + "w", + encoding="utf-8", + ) as f: + f.writelines( + "\n".join( + [ + single_cmd.cmd + for single_cmd in midi_cvt.form_java_progress_bar( + maxscore, scoreboard_name, progressbar_style + ) + ] + ) + ) + + index_file.close() + + if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.zip"): + os.remove(f"{dist_path}/{midi_cvt.music_name}.zip") + compress_zipfile( + f"{dist_path}/temp/", + f"{dist_path}/{midi_cvt.music_name}[JEscore].zip", + ) + + shutil.rmtree(f"{dist_path}/temp/") + + return maxlen, maxscore + + +print( + to_zip_pack_in_score( + Musicreater.experiment.FutureMidiConvertJavaE.from_midi_file( + input("midi路径:"), + play_speed=float(input("播放速度:")), + old_exe_format=True, + ), + input("输出路径:"), + Musicreater.experiment.ProgressBarStyle(), + # Musicreater.plugin.ConvertConfig(input("输出路径:"),), + scoreboard_name=input("计分板名称:"), + sound_source=input("发音源:"), + auto_reset=True, + ) +) diff --git a/requirements.txt b/requirements.txt index dab89c8..9e40f94 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ mido>=1.3 +xxhash>=3 \ No newline at end of file