diff --git a/.gitignore b/.gitignore index c6f0fec..b632acd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ *.mcpack *.bdx *.json +*.mcstructure # Byte-compiled / optimized __pycache__/ diff --git a/Musicreater/__init__.py b/Musicreater/__init__.py index a42b215..c0c16c0 100644 --- a/Musicreater/__init__.py +++ b/Musicreater/__init__.py @@ -19,8 +19,6 @@ Terms & Conditions: ../License.md from .main import * -__version__ = "0.4.0" +__version__ = "0.5.0" __all__ = [] -__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray")) - - +__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon")) diff --git a/Musicreater/exceptions.py b/Musicreater/exceptions.py index 9db92b9..d0bc999 100644 --- a/Musicreater/exceptions.py +++ b/Musicreater/exceptions.py @@ -25,6 +25,7 @@ class MSCTBaseException(Exception): """音·创库版本的所有错误均继承于此""" def __init__(self, *args): + """音·创库版本的所有错误均继承于此""" super().__init__(*args) def miao( @@ -35,39 +36,67 @@ class MSCTBaseException(Exception): def crash_it(self): raise self - - -class CrossNoteError(MSCTBaseException): - """同通道下同音符交叉出现所产生的错误""" - - pass -class NotDefineTempoError(MSCTBaseException): - """没有Tempo设定导致时间无法计算的错误""" +class MidiFormatException(MSCTBaseException): + """音·创库版本的所有MIDI格式错误均继承于此""" - pass + def __init__(self, *args): + """音·创库版本的所有MIDI格式错误均继承于此""" + super().__init__("MIDI格式错误", *args) class MidiDestroyedError(MSCTBaseException): """Midi文件损坏""" - pass + def __init__(self, *args): + """Midi文件损坏""" + super().__init__("MIDI文件损坏:无法读取MIDI文件", *args) -class ChannelOverFlowError(MSCTBaseException): - """一个midi中含有过多的通道(数量应≤16)""" +class CommandFormatError(RuntimeError): + """指令格式与目标格式不匹配而引起的错误""" - pass + def __init__(self, *args): + """指令格式与目标格式不匹配而引起的错误""" + super().__init__("指令格式不匹配", *args) -class NotDefineProgramError(MSCTBaseException): +class CrossNoteError(MidiFormatException): + """同通道下同音符交叉出现所产生的错误""" + + def __init__(self, *args): + """同通道下同音符交叉出现所产生的错误""" + super().__init__("同通道下同音符交叉", *args) + + +class NotDefineTempoError(MidiFormatException): + """没有Tempo设定导致时间无法计算的错误""" + + def __init__(self, *args): + """没有Tempo设定导致时间无法计算的错误""" + super().__init__("在曲目开始时没有声明Tempo(未指定拍长)", *args) + + +class ChannelOverFlowError(MidiFormatException): + """一个midi中含有过多的通道""" + + def __init__(self, max_channel = 16, *args): + """一个midi中含有过多的通道""" + super().__init__("含有过多的通道(数量应≤{})".format(max_channel), *args) + + +class NotDefineProgramError(MidiFormatException): """没有Program设定导致没有乐器可以选择的错误""" - pass + def __init__(self, *args): + """没有Program设定导致没有乐器可以选择的错误""" + super().__init__("未指定演奏乐器", *args) -class ZeroSpeedError(MSCTBaseException): +class ZeroSpeedError(MidiFormatException): """以0作为播放速度的错误""" - pass \ No newline at end of file + def __init__(self, *args): + """以0作为播放速度的错误""" + super().__init__("播放速度为0", *args) diff --git a/Musicreater/instConstants.py b/Musicreater/instConstants.py index d8a2615..8ddbd40 100644 --- a/Musicreater/instConstants.py +++ b/Musicreater/instConstants.py @@ -176,4 +176,25 @@ percussion_instrument_list = { 77: ("note.xylophone", 4), 78: ("note.xylophone", 4), 79: ("note.bell", 4), - 80: ("note.bell", 4), } + 80: ("note.bell", 4), +} + +instrument_to_blocks_list = { + "note.bass": ("planks",), + "note.snare": ("sand",), + "note.hat": ("glass",), + "note.bd": ("stone",), + "note.bell": ("gold_block",), + "note.flute": ("clay",), + "note.chime": ("packed_ice",), + "note.guitar": ("wool",), + "note.xylobone": ("bone_block",), + "note.iron_xylophone": ("iron_block",), + "note.cow_bell": ("soul_sand",), + "note.didgeridoo": ("pumpkin",), + "note.bit": ("emerald_block",), + "note.banjo": ("hay_block",), + "note.pling": ("glowstone",), + "note.bassattack": ("command_block",), # 无法找到此音效 + "note.harp": ("glass",), +} diff --git a/Musicreater/magicmain.py b/Musicreater/magicmain.py index 7b2a3cf..b066390 100644 --- a/Musicreater/magicmain.py +++ b/Musicreater/magicmain.py @@ -1,35 +1,38 @@ # -*- coding: utf-8 -*- - +""" +功能测试 若非已知 请勿更改 +此文件仅供功能测试,并非实际调用的文件 +请注意,此处的文件均为测试使用 +不要更改 不要更改 不要更改 +请注意这里的一切均需要其原作者更改 +这里用于放置一些新奇的点子 +用于测试 +不要更改 不要更改 不要更改! +""" # 音·创 开发交流群 861684859 +# Email TriM-Organization@hotmail.com # 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon") -# 若需使用或借鉴 请依照 Apache 2.0 许可证进行许可 +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md """ -音·创 库版 (Musicreater Package Version) -是一款免费开源的针对《我的世界:基岩版》的midi音乐转换库 -注意!除了此源文件以外,任何属于此仓库以及此项目的文件均依照Apache许可证进行许可 -Musicreater pkgver (Package Version 音·创 库版) -A free open source library used for convert midi file into formats that is suitable for **Minecraft: Bedrock Edition**. -Note! Except for this source file, all the files in this repository and this project are licensed under Apache License 2.0 +音·创 (Musicreater) +是一款免费开源的针对《我的世界》的midi音乐转换库 +Musicreater (音·创) +A free open source library used for convert midi file into formats that is suitable for **Minecraft**. - Copyright 2022 all the developers of Musicreater +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater - Licensed under the Apache License, Version 2.0 (the 'License'); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an 'AS IS' BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +开源相关声明请见 ../License.md +Terms & Conditions: ../License.md """ + + + def _toCmdList_m1( self, scoreboardname: str = "mscplay", @@ -91,10 +94,6 @@ def _toCmdList_m1( - - - - # ============================ @@ -102,10 +101,6 @@ def _toCmdList_m1( import mido - - - - class NoteMessage: def __init__(self, channel, pitch, velocity, startT, lastT, midi, now_bpm, change_bpm=None): self.channel = channel @@ -213,5 +208,98 @@ if __name__ == '__main__': +# ============================ + + +from typing import Union +from .utils import x,y,z,bottem_side_length_of_smallest_square_bottom_box,form_note_block_in_NBT_struct,form_repeater_in_NBT_struct + +# 不要用 没写完 +def delay_to_note_blocks( + baseblock: str = "stone", + position_forward: Union(x, y, z) = z, + max_height: int = 64, +): + """传入音符,生成以音符盒存储的红石音乐 + :param: + baseblock: 中继器的下垫方块 + position_forward: 结构延长方向 + :return 是否生成成功 + """ + + from TrimMCStruct import Structure, Block + + _sideLength = bottem_side_length_of_smallest_square_bottom_box( + len(commands), max_height + ) + + struct = Structure( + (_sideLength, max_height, _sideLength), # 声明结构大小 + ) + + log = print + + startpos = [0,0,0] + + + # 1拍 x 2.5 rt + def placeNoteBlock(): + for i in notes: + error = True + try: + struct.set_block( + [startpos[0], startpos[1] + 1, startpos[2]], + form_note_block_in_NBT_struct(height2note[i[0]], instrument), + ) + struct.set_block(startpos, Block("universal_minecraft", instuments[i[0]][1]),) + error = False + except ValueError: + log("无法放置音符:" + str(i) + "于" + str(startpos)) + struct.set_block(Block("universal_minecraft", baseblock), startpos) + struct.set_block( + Block("universal_minecraft", baseblock), + [startpos[0], startpos[1] + 1, startpos[2]], + ) + finally: + if error is True: + log("无法放置音符:" + str(i) + "于" + str(startpos)) + struct.set_block(Block("universal_minecraft", baseblock), startpos) + struct.set_block( + Block("universal_minecraft", baseblock), + [startpos[0], startpos[1] + 1, startpos[2]], + ) + delay = int(i[1] * speed + 0.5) + if delay <= 4: + startpos[0] += 1 + struct.set_block( + form_repeater_in_NBT_struct(delay, "west"), + [startpos[0], startpos[1] + 1, startpos[2]], + ) + struct.set_block(Block("universal_minecraft", baseblock), startpos) + else: + for j in range(int(delay / 4)): + startpos[0] += 1 + struct.set_block( + form_repeater_in_NBT_struct(4, "west"), + [startpos[0], startpos[1] + 1, startpos[2]], + ) + struct.set_block(Block("universal_minecraft", baseblock), startpos) + if delay % 4 != 0: + startpos[0] += 1 + struct.set_block( + form_repeater_in_NBT_struct(delay % 4, "west"), + [startpos[0], startpos[1] + 1, startpos[2]], + ) + struct.set_block(Block("universal_minecraft", baseblock), startpos) + startpos[0] += posadder[0] + startpos[1] += posadder[1] + startpos[2] += posadder[2] + + # e = True + try: + placeNoteBlock() + # e = False + except: # ValueError + log("无法放置方块了,可能是因为区块未加载叭") diff --git a/Musicreater/main.py b/Musicreater/main.py index 1cc3d47..e0f08a4 100644 --- a/Musicreater/main.py +++ b/Musicreater/main.py @@ -35,6 +35,11 @@ from typing import TypeVar, Union T = TypeVar("T") # Declare type variable VM = TypeVar("VM", mido.MidiFile, None) # void mido +DEFAULT_PROGRESSBAR_STYLE = ( + r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]", + ("§e=§r", "§7=§r"), +) + class SingleNote: def __init__( @@ -92,13 +97,16 @@ class SingleNote: class MethodList(list): + """函数列表,列表中的所有元素均为函数""" + def __init__(self, in_=()): + """函数列表,列表中的所有元素均为函数""" super().__init__() self._T = [_x for _x in in_] def __getitem__(self, item) -> T: return self._T[item] - + def __len__(self) -> int: return self._T.__len__() @@ -138,18 +146,36 @@ tick * tempo / 1000000.0 / ticks_per_beat * 一秒多少游戏刻 class midiConvert: - def __init__(self, debug: bool = False): + def __init__(self, enable_old_exe_format: bool = True, debug: bool = False): """简单的midi转换类,将midi文件转换为我的世界结构或者包""" - self.debugMode: bool = debug - self.midiFile: str = "" + self.debug_mode: bool = debug + """是否开启调试模式""" + + self.midi_file: str = "" + """Midi文件路径""" + self.midi: VM = None - self.outputPath: str = "" - self.midFileName: str = "" - self.exeHead = "" + """MidiFile对象""" + + self.output_path: str = "" + """输出路径""" + + self.mid_file_name: str = "" + """文件名,不含路径且不含后缀""" + + self.execute_cmd_head = "" + """execute 指令的执行开头,用于被format""" + self.methods = MethodList( - [self._toCmdList_m1, self._toCmdList_m2, self._toCmdList_m3, self._toCmdList_m4] + [ + self._toCmdList_m1, + self._toCmdList_m2, + self._toCmdList_m3, + self._toCmdList_m4, + ] ) + """转换算法列表,你觉得我为什么要这样调用函数?""" self.methods_byDelay = MethodList( [ @@ -158,53 +184,79 @@ class midiConvert: self._toCmdList_withDelay_m3, ] ) + """转换算法列表,但是是对于延迟播放器的,你觉得我为什么要这样调用函数?""" - def convert(self, midiFile: str, outputPath: str, oldExeFormat: bool = True): - """转换前需要先运行此函数来获取基本信息""" + self.enable_old_exe_format = enable_old_exe_format + """是否启用旧版指令格式""" - self.midiFile = midiFile - """midi文件路径""" - - try: - self.midi = mido.MidiFile(self.midiFile) - """MidiFile对象""" - except Exception as E: - raise MidiDestroyedError(f"文件{self.midiFile}损坏:{E}") - - self.outputPath = os.path.abspath(outputPath) - """输出路径""" - # 将self.midiFile的文件名,不含路径且不含后缀存入self.midiFileName - self.midFileName = os.path.splitext(os.path.basename(self.midiFile))[0] - """文件名,不含路径且不含后缀""" - - self.exeHead = ( + self.execute_cmd_head = ( "execute {} ~ ~ ~ " - if oldExeFormat + if enable_old_exe_format else "execute as {} at @s positioned ~ ~ ~ run " ) """execute指令的应用,两个版本提前决定。""" + def convert(self, midi_file: str, output_path: str): + """转换前需要先运行此函数来获取基本信息""" + + self.midi_file = midi_file + """midi文件路径""" + + try: + self.midi = mido.MidiFile(self.midi_file) + """MidiFile对象""" + except Exception as E: + raise MidiDestroyedError(f"文件{self.midi_file}损坏:{E}") + + self.output_path = os.path.abspath(output_path) + """输出路径""" + # 将self.midiFile的文件名,不含路径且不含后缀存入self.midiFileName + self.mid_file_name = os.path.splitext(os.path.basename(self.midi_file))[0] + """文件名,不含路径且不含后缀""" + @staticmethod - def __Inst2soundID_withX(instrumentID): - """返回midi的乐器ID对应的我的世界乐器名,对于音域转换算法,如下: - 2**( ( msg.note - 60 - X ) / 12 ) 即为MC的音高,其中 - X的取值随乐器不同而变化: - 竖琴harp、电钢琴pling、班卓琴banjo、方波bit、颤音琴iron_xylophone 的时候为6 - 吉他的时候为7 - 贝斯bass、迪吉里杜管didgeridoo的时候为8 - 长笛flute、牛铃cou_bell的时候为5 - 钟琴bell、管钟chime、木琴xylophone的时候为4 - 而存在一些打击乐器bd(basedrum)、hat、snare,没有音域,则没有X,那么我们返回7即可 - :param instrumentID: midi的乐器ID - default: 如果instrumentID不在范围内,返回的默认我的世界乐器名称 - :return: (str我的世界乐器名, int转换算法中的X)""" + def __Inst2soundID_withX( + instrumentID: int, + ): + """ + 返回midi的乐器ID对应的我的世界乐器名,对于音域转换算法,如下: + 2**( ( msg.note - 60 - X ) / 12 ) 即为MC的音高,其中 + X的取值随乐器不同而变化: + 竖琴harp、电钢琴pling、班卓琴banjo、方波bit、颤音琴iron_xylophone 的时候为6 + 吉他的时候为7 + 贝斯bass、迪吉里杜管didgeridoo的时候为8 + 长笛flute、牛铃cou_bell的时候为5 + 钟琴bell、管钟chime、木琴xylophone的时候为4 + 而存在一些打击乐器bd(basedrum)、hat、snare,没有音域,则没有X,那么我们返回7即可 + + Parameters + ---------- + instrumentID: int + midi的乐器ID + + Returns + ------- + tuple(str我的世界乐器名, int转换算法中的X) + """ try: return pitched_instrument_list[instrumentID] except KeyError: return "note.flute", 5 @staticmethod - def __bitInst2ID_withX(instrumentID): + def __bitInst2ID_withX(instrumentID: int): + """ + 对于Midi第10通道所对应的打击乐器,返回我的世界乐器名 + + Parameters + ---------- + instrumentID: int + midi的乐器ID + + Returns + ------- + tuple(str我的世界乐器名, int转换算法中的X) + """ try: return percussion_instrument_list[instrumentID] except KeyError: @@ -220,19 +272,36 @@ class midiConvert: @staticmethod def score2time(score: int): + """ + 将《我的世界》的计分(以游戏刻计)转为表示时间的字符串 + """ return str(int(int(score / 20) / 60)) + ":" + str(int(int(score / 20) % 60)) - def __formProgressBar( + def __form_progress_bar( self, - maxscore: int, + max_score: int, scoreboard_name: str, - progressbar: tuple = ( - r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]", - ("§e=§r", "§7=§r"), - ), + progressbar_style: tuple = DEFAULT_PROGRESSBAR_STYLE, ) -> list: + """ + 生成进度条 - pgs_style = progressbar[0] + Parameters + ---------- + maxscore: int + midi的乐器ID + + scoreboard_name: str + 所使用的计分板名称 + + progressbar_style: tuple + 此参数详见 ../docs/库的生成与功能文档.md#进度条自定义 + + Returns + ------- + list[str"指令",] + """ + pgs_style = progressbar_style[0] """用于被替换的进度条原始样式""" """ @@ -246,15 +315,15 @@ class midiConvert: | `%%%` | 当前进度比率 | | `_` | 用以表示进度条占位| """ - perEach = maxscore / pgs_style.count("_") + perEach = max_score / pgs_style.count("_") result = [] if r"%^s" in pgs_style: - pgs_style = pgs_style.replace(r"%^s", str(maxscore)) + pgs_style = pgs_style.replace(r"%^s", str(max_score)) if r"%^t" in pgs_style: - pgs_style = pgs_style.replace(r"%^t", self.score2time(maxscore)) + pgs_style = pgs_style.replace(r"%^t", self.score2time(max_score)) sbn_pc = scoreboard_name[:2] if r"%%%" in pgs_style: @@ -262,29 +331,29 @@ class midiConvert: 'scoreboard objectives add {}PercT dummy "百分比计算"'.format(sbn_pc) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players set MaxScore {} {}".format( - scoreboard_name, maxscore + scoreboard_name, max_score ) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players set n100 {} 100".format(scoreboard_name) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players operation @s {} = @s {}".format( sbn_pc + "PercT", scoreboard_name ) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players operation @s {} *= n100 {}".format( sbn_pc + "PercT", scoreboard_name ) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players operation @s {} /= MaxScore {}".format( sbn_pc + "PercT", scoreboard_name ) @@ -298,47 +367,47 @@ class midiConvert: 'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(sbn_pc) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players set n20 {} 20".format(scoreboard_name) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players set n60 {} 60".format(scoreboard_name) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players operation @s {} = @s {}".format( sbn_pc + "TMinT", scoreboard_name ) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players operation @s {} /= n20 {}".format( sbn_pc + "TMinT", scoreboard_name ) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players operation @s {} /= n60 {}".format( sbn_pc + "TMinT", scoreboard_name ) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players operation @s {} = @s {}".format( sbn_pc + "TSecT", scoreboard_name ) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players operation @s {} /= n20 {}".format( sbn_pc + "TSecT", scoreboard_name ) ) result.append( - self.exeHead.format("@a[scores={" + scoreboard_name + "=1..}]") + self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") + "scoreboard players operation @s {} %= n60 {}".format( sbn_pc + "TSecT", scoreboard_name ) @@ -346,12 +415,12 @@ class midiConvert: for i in range(pgs_style.count("_")): npg_stl = ( - pgs_style.replace("_", progressbar[1][0], i + 1) - .replace("_", progressbar[1][1]) - .replace(r"%%N", self.midFileName) + pgs_style.replace("_", progressbar_style[1][0], i + 1) + .replace("_", progressbar_style[1][1]) + .replace(r"%%N", self.mid_file_name) if r"%%N" in pgs_style - else pgs_style.replace("_", progressbar[1][0], i + 1).replace( - "_", progressbar[1][1] + else pgs_style.replace("_", progressbar_style[1][0], i + 1).replace( + "_", progressbar_style[1][1] ) ) if r"%%s" in npg_stl: @@ -377,7 +446,7 @@ class midiConvert: ), ) result.append( - self.exeHead.format( + self.execute_cmd_head.format( r"@a[scores={" + scoreboard_name + f"={int(i * perEach)}..{math.ceil((i + 1) * perEach)}" @@ -411,7 +480,7 @@ class midiConvert: # :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 tracks = [] if speed == 0: - if self.debugMode: + if self.debug_mode: raise ZeroSpeedError("播放速度仅可为正实数") speed = 1 MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) @@ -422,7 +491,6 @@ class midiConvert: # 分轨的思路其实并不好,但这个算法就是这样 # 所以我建议用第二个方法 _toCmdList_m2 for i, track in enumerate(self.midi.tracks): - ticks = 0 instrumentID = 0 singleTrack = [] @@ -481,7 +549,7 @@ class midiConvert: """ if speed == 0: - if self.debugMode: + if self.debug_mode: raise ZeroSpeedError("播放速度仅可为正实数") speed = 1 MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) @@ -513,7 +581,7 @@ class midiConvert: for msg in self.midi: microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样 if not msg.is_meta: - if self.debugMode: + if self.debug_mode: try: if msg.channel > 15: raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") @@ -563,7 +631,6 @@ class midiConvert: nowTrack = [] for msg in channels[i]: - if msg[0] == "PgmC": InstID = msg[1] @@ -575,7 +642,7 @@ class midiConvert: else self.__Inst2soundID_withX(InstID) ) except UnboundLocalError as E: - if self.debugMode: + if self.debug_mode: raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") else: soundID, _X = ( @@ -587,7 +654,7 @@ class midiConvert: maxScore = max(maxScore, score_now) nowTrack.append( - self.exeHead.format( + self.execute_cmd_head.format( "@a[scores=({}={})]".format(scoreboard_name, score_now) .replace("(", r"{") .replace(")", r"}") @@ -618,50 +685,73 @@ class midiConvert: """ if speed == 0: - if self.debugMode: + if self.debug_mode: raise ZeroSpeedError("播放速度仅可为正实数") speed = 1 MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 - channels = {0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}, 9: {}, 10: {}, 11: {}, 12: {}, 13: {}, 14: {}, 15: {}, 16: {}} + channels = { + 0: {}, + 1: {}, + 2: {}, + 3: {}, + 4: {}, + 5: {}, + 6: {}, + 7: {}, + 8: {}, + 9: {}, + 10: {}, + 11: {}, + 12: {}, + 13: {}, + 14: {}, + 15: {}, + 16: {}, + } # 我们来用通道统计音乐信息 # 但是是用分轨的思路的 for track_no, track in enumerate(self.midi.tracks): - microseconds = 0 for msg in track: - if msg.time != 0: try: - microseconds += msg.time * tempo / self.midi.ticks_per_beat / 1000 + microseconds += ( + msg.time * tempo / self.midi.ticks_per_beat / 1000 + ) # print(microseconds) except NameError: - if self.debugMode: + if self.debug_mode: raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") else: - microseconds += (msg.time * mido.midifiles.midifiles.DEFAULT_TEMPO / self.midi.ticks_per_beat) / 1000 + microseconds += ( + msg.time + * mido.midifiles.midifiles.DEFAULT_TEMPO + / self.midi.ticks_per_beat + ) / 1000 if msg.is_meta: if msg.type == "set_tempo": tempo = msg.tempo - if self.debugMode: + if self.debug_mode: self.prt(f"TEMPO更改:{tempo}(毫秒每拍)") else: - - if self.debugMode: + if self.debug_mode: try: if msg.channel > 15: raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") except AttributeError: pass - + if not track_no in channels[msg.channel].keys(): channels[msg.channel][track_no] = [] if msg.type == "program_change": - channels[msg.channel][track_no].append(("PgmC", msg.program, microseconds)) + channels[msg.channel][track_no].append( + ("PgmC", msg.program, microseconds) + ) elif msg.type == "note_on" and msg.velocity != 0: channels[msg.channel][track_no].append( @@ -671,7 +761,9 @@ class midiConvert: elif (msg.type == "note_on" and msg.velocity == 0) or ( msg.type == "note_off" ): - channels[msg.channel][track_no].append(("NoteE", msg.note, microseconds)) + channels[msg.channel][track_no].append( + ("NoteE", msg.note, microseconds) + ) """整合后的音乐通道格式 每个通道包括若干消息元素其中逃不过这三种: @@ -694,14 +786,13 @@ class midiConvert: # 如果当前通道为空 则跳过 if not channels[i]: continue - + # 第十通道是打击乐通道 SpecialBits = True if i == 9 else False # nowChannel = [] - for track_no,track in channels[i].items(): - + for track_no, track in channels[i].items(): nowTrack = [] for msg in track: @@ -716,7 +807,7 @@ class midiConvert: else self.__Inst2soundID_withX(InstID) ) except UnboundLocalError as E: - if self.debugMode: + if self.debug_mode: raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") else: soundID, _X = ( @@ -728,7 +819,7 @@ class midiConvert: maxScore = max(maxScore, score_now) nowTrack.append( - self.exeHead.format( + self.execute_cmd_head.format( "@a[scores=({}={})]".format(scoreboard_name, score_now) .replace("(", r"{") .replace(")", r"}") @@ -761,7 +852,7 @@ class midiConvert: # TODO: 这里的时间转换不知道有没有问题 if speed == 0: - if self.debugMode: + if self.debug_mode: raise ZeroSpeedError("播放速度仅可为正实数") speed = 1 MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) @@ -771,11 +862,9 @@ class midiConvert: # 我们来用通道统计音乐信息 for i, track in enumerate(self.midi.tracks): - microseconds = 0 for msg in track: - if msg.time != 0: try: microseconds += msg.time * tempo / self.midi.ticks_per_beat @@ -786,8 +875,7 @@ class midiConvert: if msg.type == "set_tempo": tempo = msg.tempo else: - - if self.debugMode: + if self.debug_mode: try: if msg.channel > 15: raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") @@ -834,7 +922,6 @@ class midiConvert: MsgIndex = [] for msg in channels[i]: - if msg[0] == "PgmC": InstID = msg[1] @@ -902,7 +989,6 @@ class midiConvert: nowTrack = [] for note in track: - for every_note in _linearFun(note): # 应该是计算的时候出了点小问题 # 我们应该用一个MC帧作为时间单位而不是半秒 @@ -947,14 +1033,13 @@ class midiConvert: tracks = {} if speed == 0: - if self.debugMode: + if self.debug_mode: raise ZeroSpeedError("播放速度仅可为正实数") speed = 1 MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) for i, track in enumerate(self.midi.tracks): - instrumentID = 0 ticks = 0 @@ -974,13 +1059,13 @@ class midiConvert: soundID, _X = self.__Inst2soundID_withX(instrumentID) try: tracks[now_tick].append( - self.exeHead.format(player) + self.execute_cmd_head.format(player) + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} " f"{2 ** ((msg.note - 60 - _X) / 12)}" ) except KeyError: tracks[now_tick] = [ - self.exeHead.format(player) + self.execute_cmd_head.format(player) + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} " f"{2 ** ((msg.note - 60 - _X) / 12)}" ] @@ -1019,7 +1104,7 @@ class midiConvert: """ tracks = {} if speed == 0: - if self.debugMode: + if self.debug_mode: raise ZeroSpeedError("播放速度仅可为正实数") speed = 1 @@ -1051,21 +1136,24 @@ class midiConvert: # 我们来用通道统计音乐信息 for msg in self.midi: try: - microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样 + microseconds += ( + msg.time * 1000 + ) # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样 # print(microseconds) except NameError: - if self.debugMode: + if self.debug_mode: raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") else: - microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样 + microseconds += ( + msg.time * 1000 + ) # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样 if msg.is_meta: if msg.type == "set_tempo": tempo = msg.tempo else: - - if self.debugMode: + if self.debug_mode: try: if msg.channel > 15: raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") @@ -1110,7 +1198,6 @@ class midiConvert: SpecialBits = False for msg in channels[i]: - if msg[0] == "PgmC": InstID = msg[1] @@ -1122,7 +1209,7 @@ class midiConvert: else self.__Inst2soundID_withX(InstID) ) except UnboundLocalError as E: - if self.debugMode: + if self.debug_mode: raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") else: soundID, _X = ( @@ -1134,13 +1221,13 @@ class midiConvert: try: tracks[score_now].append( - self.exeHead.format(player) + self.execute_cmd_head.format(player) + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} " f"{2 ** ((msg[1] - 60 - _X) / 12)}" ) except KeyError: tracks[score_now] = [ - self.exeHead.format(player) + self.execute_cmd_head.format(player) + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} " f"{2 ** ((msg[1] - 60 - _X) / 12)}" ] @@ -1179,52 +1266,75 @@ class midiConvert: :param player: 玩家选择器,默认为`@a` :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...] """ - + if speed == 0: - if self.debugMode: + if self.debug_mode: raise ZeroSpeedError("播放速度仅可为正实数") speed = 1 MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 - channels = {0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}, 9: {}, 10: {}, 11: {}, 12: {}, 13: {}, 14: {}, 15: {}, 16: {}} + channels = { + 0: {}, + 1: {}, + 2: {}, + 3: {}, + 4: {}, + 5: {}, + 6: {}, + 7: {}, + 8: {}, + 9: {}, + 10: {}, + 11: {}, + 12: {}, + 13: {}, + 14: {}, + 15: {}, + 16: {}, + } # 我们来用通道统计音乐信息 # 但是是用分轨的思路的 for track_no, track in enumerate(self.midi.tracks): - microseconds = 0 for msg in track: - if msg.time != 0: try: - microseconds += msg.time * tempo / self.midi.ticks_per_beat / 1000 + microseconds += ( + msg.time * tempo / self.midi.ticks_per_beat / 1000 + ) # print(microseconds) except NameError: - if self.debugMode: + if self.debug_mode: raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") else: - microseconds += (msg.time * mido.midifiles.midifiles.DEFAULT_TEMPO / self.midi.ticks_per_beat) / 1000 + microseconds += ( + msg.time + * mido.midifiles.midifiles.DEFAULT_TEMPO + / self.midi.ticks_per_beat + ) / 1000 if msg.is_meta: if msg.type == "set_tempo": tempo = msg.tempo - if self.debugMode: + if self.debug_mode: self.prt(f"TEMPO更改:{tempo}(毫秒每拍)") else: - - if self.debugMode: + if self.debug_mode: try: if msg.channel > 15: raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") except AttributeError: pass - + if not track_no in channels[msg.channel].keys(): channels[msg.channel][track_no] = [] if msg.type == "program_change": - channels[msg.channel][track_no].append(("PgmC", msg.program, microseconds)) + channels[msg.channel][track_no].append( + ("PgmC", msg.program, microseconds) + ) elif msg.type == "note_on" and msg.velocity != 0: channels[msg.channel][track_no].append( @@ -1234,7 +1344,9 @@ class midiConvert: elif (msg.type == "note_on" and msg.velocity == 0) or ( msg.type == "note_off" ): - channels[msg.channel][track_no].append(("NoteE", msg.note, microseconds)) + channels[msg.channel][track_no].append( + ("NoteE", msg.note, microseconds) + ) """整合后的音乐通道格式 每个通道包括若干消息元素其中逃不过这三种: @@ -1255,16 +1367,14 @@ class midiConvert: # 如果当前通道为空 则跳过 if not channels[i]: continue - + # 第十通道是打击乐通道 SpecialBits = True if i == 9 else False # nowChannel = [] - for track_no,track in channels[i].items(): - + for track_no, track in channels[i].items(): for msg in track: - if msg[0] == "PgmC": InstID = msg[1] @@ -1276,7 +1386,7 @@ class midiConvert: else self.__Inst2soundID_withX(InstID) ) except UnboundLocalError as E: - if self.debugMode: + if self.debug_mode: raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") else: soundID, _X = ( @@ -1288,13 +1398,13 @@ class midiConvert: try: tracks[score_now].append( - self.exeHead.format(player) + self.execute_cmd_head.format(player) + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} " f"{2 ** ((msg[1] - 60 - _X) / 12)}" ) except KeyError: tracks[score_now] = [ - self.exeHead.format(player) + self.execute_cmd_head.format(player) + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} " f"{2 ** ((msg[1] - 60 - _X) / 12)}" ] @@ -1307,7 +1417,15 @@ class midiConvert: results.append( ( tracks[all_ticks[i]][j], - (0 if j != 0 else (all_ticks[i] - all_ticks[i - 1] if i != 0 else all_ticks[i])), + ( + 0 + if j != 0 + else ( + all_ticks[i] - all_ticks[i - 1] + if i != 0 + else all_ticks[i] + ) + ), ) ) @@ -1341,55 +1459,55 @@ class midiConvert: # return (False, f"无法找到算法ID{method}对应的转换算法") # 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目,然后创建 - if os.path.exists(f"{self.outputPath}/temp/functions/"): - shutil.rmtree(f"{self.outputPath}/temp/functions/") - os.makedirs(f"{self.outputPath}/temp/functions/mscplay") + if os.path.exists(f"{self.output_path}/temp/functions/"): + shutil.rmtree(f"{self.output_path}/temp/functions/") + os.makedirs(f"{self.output_path}/temp/functions/mscplay") # 写入manifest.json - if not os.path.exists(f"{self.outputPath}/temp/manifest.json"): + if not os.path.exists(f"{self.output_path}/temp/manifest.json"): with open( - f"{self.outputPath}/temp/manifest.json", "w", encoding="utf-8" + f"{self.output_path}/temp/manifest.json", "w", encoding="utf-8" ) as f: f.write( '{\n "format_version": 1,\n "header": {\n "description": "' - + self.midFileName + + self.mid_file_name + ' Pack : behavior pack",\n "version": [ 0, 0, 1 ],\n "name": "' - + self.midFileName + + self.mid_file_name + 'Pack",\n "uuid": "' + str(uuid.uuid4()) + '"\n },\n "modules": [\n {\n "description": "' - + f"the Player of the Music {self.midFileName}" + + f"the Player of the Music {self.mid_file_name}" + '",\n "type": "data",\n "version": [ 0, 0, 1 ],\n "uuid": "' + str(uuid.uuid4()) + '"\n }\n ]\n}' ) else: with open( - f"{self.outputPath}/temp/manifest.json", "r", encoding="utf-8" + f"{self.output_path}/temp/manifest.json", "r", encoding="utf-8" ) as manifest: data = json.loads(manifest.read()) data["header"][ "description" - ] = f"the Player of the Music {self.midFileName}" - data["header"]["name"] = self.midFileName + ] = f"the Player of the Music {self.mid_file_name}" + data["header"]["name"] = self.mid_file_name data["header"]["uuid"] = str(uuid.uuid4()) data["modules"][0]["description"] = "None" data["modules"][0]["uuid"] = str(uuid.uuid4()) manifest.close() - open(f"{self.outputPath}/temp/manifest.json", "w", encoding="utf-8").write( + open(f"{self.output_path}/temp/manifest.json", "w", encoding="utf-8").write( json.dumps(data) ) # 将命令列表写入文件 index_file = open( - f"{self.outputPath}/temp/functions/index.mcfunction", "w", encoding="utf-8" + f"{self.output_path}/temp/functions/index.mcfunction", "w", encoding="utf-8" ) for track in cmdlist: index_file.write( "function mscplay/track" + str(cmdlist.index(track) + 1) + "\n" ) with open( - f"{self.outputPath}/temp/functions/mscplay/track{cmdlist.index(track) + 1}.mcfunction", + f"{self.output_path}/temp/functions/mscplay/track{cmdlist.index(track) + 1}.mcfunction", "w", encoding="utf-8", ) as f: @@ -1416,24 +1534,26 @@ class midiConvert: ) if progressbar: - if progressbar: + # 此处是对于仅有 True 的参数和自定义参数的判断 + # 改这一行没🐎 + if progressbar is True: with open( - f"{self.outputPath}/temp/functions/mscplay/progressShow.mcfunction", + f"{self.output_path}/temp/functions/mscplay/progressShow.mcfunction", "w", encoding="utf-8", ) as f: f.writelines( - "\n".join(self.__formProgressBar(maxscore, scoreboard_name)) + "\n".join(self.__form_progress_bar(maxscore, scoreboard_name)) ) else: with open( - f"{self.outputPath}/temp/functions/mscplay/progressShow.mcfunction", + f"{self.output_path}/temp/functions/mscplay/progressShow.mcfunction", "w", encoding="utf-8", ) as f: f.writelines( "\n".join( - self.__formProgressBar( + self.__form_progress_bar( maxscore, scoreboard_name, progressbar ) ) @@ -1441,17 +1561,18 @@ class midiConvert: index_file.close() - if os.path.exists(f"{self.outputPath}/{self.midFileName}.mcpack"): - os.remove(f"{self.outputPath}/{self.midFileName}.mcpack") + if os.path.exists(f"{self.output_path}/{self.mid_file_name}.mcpack"): + os.remove(f"{self.output_path}/{self.mid_file_name}.mcpack") compress_zipfile( - f"{self.outputPath}/temp/", f"{self.outputPath}/{self.midFileName}.mcpack" + f"{self.output_path}/temp/", + f"{self.output_path}/{self.mid_file_name}.mcpack", ) - shutil.rmtree(f"{self.outputPath}/temp/") + shutil.rmtree(f"{self.output_path}/temp/") return True, maxlen, maxscore - def to_mcstructure_file_with_delay( + def to_mcpack_with_delay( self, method: int = 1, volume: float = 1.0, @@ -1461,7 +1582,7 @@ class midiConvert: max_height: int = 64, ): """ - 使用method指定的转换算法,将midi转换为BDX结构文件 + 使用method指定的转换算法,将midi转换为mcstructure结构文件后打包成mcpack文件 :param method: 转换算法 :param author: 作者名称 :param progressbar: 进度条,(当此参数为True时使用默认进度条,为其他的值为真的参数时识别为进度条自定义参数,为其他值为假的时候不生成进度条) @@ -1471,24 +1592,224 @@ class midiConvert: :param player: 玩家选择器,默认为`@a` :return 成功与否,成功返回(True,未经过压缩的源,结构占用大小),失败返回(False,str失败原因) """ - cmdlist, max_delay = self.methods_byDelay[method - 1]( + + if self.enable_old_exe_format: + raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。") + + command_list, max_delay = self.methods_byDelay[method - 1]( volume, speed, player, ) - if not os.path.exists(self.outputPath): - os.makedirs(self.outputPath) - - struct, size = to_structure(cmdlist,max_height-1) + # 此处是对于仅有 True 的参数和自定义参数的判断 + # 改这一行没🐎 + if progressbar is True: + progressbar = DEFAULT_PROGRESSBAR_STYLE + if not os.path.exists(self.output_path): + os.makedirs(self.output_path) + + # 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建 + if os.path.exists(f"{self.output_path}/temp/"): + shutil.rmtree(f"{self.output_path}/temp/") + os.makedirs(f"{self.output_path}/temp/functions/") + os.makedirs(f"{self.output_path}/temp/structures/") + + # 写入manifest.json + with open(f"{self.output_path}/temp/manifest.json", "w", encoding="utf-8") as f: + json.dump( + { + "format_version": 1, + "header": { + "description": f"the Music {self.mid_file_name}", + "version": [0, 0, 1], + "name": self.mid_file_name, + "uuid": str(uuid.uuid4()), + }, + "modules": [ + { + "description": "Ryoun mub Pack : behavior pack", + "type": "data", + "version": [0, 0, 1], + "uuid": str(uuid.uuid4()), + } + ], + }, + fp=f, + ) + + # 将命令列表写入文件 + index_file = open( + f"{self.output_path}/temp/functions/index.mcfunction", "w", encoding="utf-8" + ) + + struct, size, end_pos = commands_to_structure(command_list, max_height - 1) with open( - os.path.abspath(os.path.join(self.outputPath, f"{self.midFileName}.mcstructure")), + os.path.abspath( + os.path.join( + self.output_path, + "temp/structures/", + f"{self.mid_file_name}_main.mcstructure", + ) + ), "wb+", ) as f: struct.dump(f) - + del struct + + if progressbar: + scb_name = self.mid_file_name[:5] + "Pgb" + index_file.write( + "scoreboard objectives add {0} dummy {0}计\n".format(scb_name) + ) + + struct_a = Structure( + (1, 1, 1), + ) + struct_a.set_block( + (0, 0, 0), + form_command_block_in_NBT_struct( + r"scoreboard players add {} {} 1".format(player, scb_name), + (0, 0, 0), + 1, + 1, + customName="显示进度条并加分", + ), + ) + + with open( + os.path.abspath( + os.path.join( + self.output_path, + "temp/structures/", + f"{self.mid_file_name}_start.mcstructure", + ) + ), + "wb+", + ) as f: + struct_a.dump(f) + + index_file.write(f"structure load {self.mid_file_name}_start ~ ~ ~1\n") + + pgb_struct, pgbSize, pgbNowPos = commands_to_structure( + [ + (i, 0) + for i in self.__form_progress_bar(max_delay, scb_name, progressbar) + ], + max_height - 1, + ) + + with open( + os.path.abspath( + os.path.join( + self.output_path, + "temp/structures/", + f"{self.mid_file_name}_pgb.mcstructure", + ) + ), + "wb+", + ) as f: + pgb_struct.dump(f) + + index_file.write(f"structure load {self.mid_file_name}_pgb ~ ~1 ~1\n") + + struct_a = Structure( + (1, 1, 1), + ) + struct_a.set_block( + (0, 0, 0), + form_command_block_in_NBT_struct( + r"scoreboard players reset {} {}".format(player, scb_name), + (0, 0, 0), + 1, + 1, + customName="重置进度条计分板", + ), + ) + + with open( + os.path.abspath( + os.path.join( + self.output_path, + "temp/structures/", + f"{self.mid_file_name}_reset.mcstructure", + ) + ), + "wb+", + ) as f: + struct_a.dump(f) + + del struct_a, pgb_struct + + index_file.write( + f"structure load {self.mid_file_name}_reset ~{pgbSize[0]+2} ~ ~1\n" + ) + + index_file.write( + f"structure load {self.mid_file_name}_main ~{pgbSize[0]+2} ~1 ~1\n" + ) + + else: + index_file.write(f"structure load {self.mid_file_name}_main ~ ~ ~1\n") + + index_file.close() + + if os.path.exists(f"{self.output_path}/{self.mid_file_name}.mcpack"): + os.remove(f"{self.output_path}/{self.mid_file_name}.mcpack") + compress_zipfile( + f"{self.output_path}/temp/", + f"{self.output_path}/{self.mid_file_name}.mcpack", + ) + + shutil.rmtree(f"{self.output_path}/temp/") + + return True, len(command_list), max_delay + + def to_mcstructure_file_with_delay( + self, + method: int = 1, + volume: float = 1.0, + speed: float = 1.0, + player: str = "@a", + max_height: int = 64, + ): + """ + 使用method指定的转换算法,将midi转换为mcstructure结构文件 + :param method: 转换算法 + :param author: 作者名称 + :param progressbar: 进度条,(当此参数为True时使用默认进度条,为其他的值为真的参数时识别为进度条自定义参数,为其他值为假的时候不生成进度条) + :param max_height: 生成结构最大高度 + :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 + :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed + :param player: 玩家选择器,默认为`@a` + :return 成功与否,成功返回(True,未经过压缩的源,结构占用大小),失败返回(False,str失败原因) + """ + + if self.enable_old_exe_format: + raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。") + + cmd_list, max_delay = self.methods_byDelay[method - 1]( + volume, + speed, + player, + ) + + if not os.path.exists(self.output_path): + os.makedirs(self.output_path) + + struct, size, end_pos = commands_to_structure(cmd_list, max_height - 1) + + with open( + os.path.abspath( + os.path.join(self.output_path, f"{self.mid_file_name}.mcstructure") + ), + "wb+", + ) as f: + struct.dump(f) + + return True, size, max_delay def to_BDX_file( self, @@ -1520,11 +1841,13 @@ class midiConvert: # except Exception as E: # return (False, f"无法找到算法ID{method}对应的转换算法: {E}") - if not os.path.exists(self.outputPath): - os.makedirs(self.outputPath) + if not os.path.exists(self.output_path): + os.makedirs(self.output_path) with open( - os.path.abspath(os.path.join(self.outputPath, f"{self.midFileName}.bdx")), + os.path.abspath( + os.path.join(self.output_path, f"{self.mid_file_name}.bdx") + ), "w+", ) as f: f.write("BD@") @@ -1550,18 +1873,20 @@ class midiConvert: + scoreboard_name, ) - cmdBytes, size, finalPos = to_BDX_bytes( + cmdBytes, size, finalPos = commands_to_BDX_bytes( [(i, 0) for i in commands], max_height - 1 ) - # 此处是对于仅有 True 的参数和自定义参数的判断 + if progressbar: - pgbBytes, pgbSize, pgbNowPos = to_BDX_bytes( + pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes( [ (i, 0) for i in ( - self.__formProgressBar(maxScore, scoreboard_name) - if progressbar - else self.__formProgressBar( + self.__form_progress_bar(maxScore, scoreboard_name) + # 此处是对于仅有 True 的参数和自定义参数的判断 + # 改这一行没🐎 + if progressbar is True + else self.__form_progress_bar( maxScore, scoreboard_name, progressbar ) ) @@ -1580,7 +1905,9 @@ class midiConvert: _bytes += cmdBytes with open( - os.path.abspath(os.path.join(self.outputPath, f"{self.midFileName}.bdx")), + os.path.abspath( + os.path.join(self.output_path, f"{self.mid_file_name}.bdx") + ), "ab+", ) as f: f.write(brotli.compress(_bytes + b"XE")) @@ -1618,11 +1945,13 @@ class midiConvert: # except Exception as E: # return (False, f"无法找到算法ID{method}对应的转换算法\n{E}") - if not os.path.exists(self.outputPath): - os.makedirs(self.outputPath) + if not os.path.exists(self.output_path): + os.makedirs(self.output_path) with open( - os.path.abspath(os.path.join(self.outputPath, f"{self.midFileName}.bdx")), + os.path.abspath( + os.path.join(self.output_path, f"{self.mid_file_name}.bdx") + ), "w+", ) as f: f.write("BD@") @@ -1634,18 +1963,16 @@ class midiConvert: ) # 此处是对于仅有 True 的参数和自定义参数的判断 - if progressbar: - progressbar = ( - r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]", - ("§e=§r", "§7=§r"), - ) + # 改这一行没🐎 + if progressbar is True: + progressbar = DEFAULT_PROGRESSBAR_STYLE - cmdBytes, size, finalPos = to_BDX_bytes(cmdlist, max_height - 1) + cmdBytes, size, finalPos = commands_to_BDX_bytes(cmdlist, max_height - 1) if progressbar: - scb_name = self.midFileName[:5] + "Pgb" + scb_name = self.mid_file_name[:5] + "Pgb" _bytes += form_command_block_in_BDX_bytes( - r"scoreboard objectives add {} dummy {}播放用".replace(r"{}", scb_name), + r"scoreboard objectives add {} dummy {}计".replace(r"{}", scb_name), 1, customName="初始化进度条", ) @@ -1657,10 +1984,10 @@ class midiConvert: customName="显示进度条并加分", ) _bytes += bdx_move(y, 1) - pgbBytes, pgbSize, pgbNowPos = to_BDX_bytes( + pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes( [ (i, 0) - for i in self.__formProgressBar(max_delay, scb_name, progressbar) + for i in self.__form_progress_bar(max_delay, scb_name, progressbar) ], max_height - 1, ) @@ -1682,7 +2009,9 @@ class midiConvert: _bytes += cmdBytes with open( - os.path.abspath(os.path.join(self.outputPath, f"{self.midFileName}.bdx")), + os.path.abspath( + os.path.join(self.output_path, f"{self.mid_file_name}.bdx") + ), "ab+", ) as f: f.write(brotli.compress(_bytes + b"XE")) @@ -1698,44 +2027,65 @@ class midiConvert: """ # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 - channels = {0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}, 9: {}, 10: {}, 11: {}, 12: {}, 13: {}, 14: {}, 15: {}, 16: {}} + channels = { + 0: {}, + 1: {}, + 2: {}, + 3: {}, + 4: {}, + 5: {}, + 6: {}, + 7: {}, + 8: {}, + 9: {}, + 10: {}, + 11: {}, + 12: {}, + 13: {}, + 14: {}, + 15: {}, + 16: {}, + } # 我们来用通道统计音乐信息 # 但是是用分轨的思路的 for track_no, track in enumerate(self.midi.tracks): - microseconds = 0 for msg in track: - if msg.time != 0: try: microseconds += msg.time * tempo / self.midi.ticks_per_beat # print(microseconds) except NameError: - if self.debugMode: + if self.debug_mode: raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") else: - microseconds += (msg.time * mido.midifiles.midifiles.DEFAULT_TEMPO / self.midi.ticks_per_beat) + microseconds += ( + msg.time + * mido.midifiles.midifiles.DEFAULT_TEMPO + / self.midi.ticks_per_beat + ) if msg.is_meta: if msg.type == "set_tempo": tempo = msg.tempo - if self.debugMode: + if self.debug_mode: self.prt(f"TEMPO更改:{tempo}(毫秒每拍)") else: - - if self.debugMode: + if self.debug_mode: try: if msg.channel > 15: raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") except AttributeError: pass - + if not track_no in channels[msg.channel].keys(): channels[msg.channel][track_no] = [] if msg.type == "program_change": - channels[msg.channel][track_no].append(("PgmC", msg.program, microseconds)) + channels[msg.channel][track_no].append( + ("PgmC", msg.program, microseconds) + ) elif msg.type == "note_on" and msg.velocity != 0: channels[msg.channel][track_no].append( @@ -1745,7 +2095,9 @@ class midiConvert: elif (msg.type == "note_on" and msg.velocity == 0) or ( msg.type == "note_off" ): - channels[msg.channel][track_no].append(("NoteE", msg.note, microseconds)) + channels[msg.channel][track_no].append( + ("NoteE", msg.note, microseconds) + ) """整合后的音乐通道格式 每个通道包括若干消息元素其中逃不过这三种: diff --git a/Musicreater/utils.py b/Musicreater/utils.py index 81fa654..e67c34f 100644 --- a/Musicreater/utils.py +++ b/Musicreater/utils.py @@ -1,7 +1,9 @@ import math import os +from typing import Union +from TrimMCStruct import Structure, Block, TAG_Long, TAG_Byte -key = { +bdx_key = { "x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"], "y": [b"\x11", b"\x10", b"\x1d", b"\x16", b"\x17"], "z": [b"\x13", b"\x12", b"\x1e", b"\x18", b"\x19"], @@ -18,7 +20,7 @@ def bdx_move(axis: str, value: int): if value == 0: return b"" if abs(value) == 1: - return key[axis][0 if value == -1 else 1] + return bdx_key[axis][0 if value == -1 else 1] pointer = sum( [ @@ -32,7 +34,9 @@ def bdx_move(axis: str, value: int): ] ) - return key[axis][pointer] + value.to_bytes(2 ** (pointer - 2), "big", signed=True) + return bdx_key[axis][pointer] + value.to_bytes( + 2 ** (pointer - 2), "big", signed=True + ) def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None): @@ -112,8 +116,7 @@ def form_command_block_in_BDX_bytes( :return:str """ - block = b"\x24" + \ - particularValue.to_bytes(2, byteorder="big", signed=False) + block = b"\x24" + particularValue.to_bytes(2, byteorder="big", signed=False) for i in [ impluse.to_bytes(4, byteorder="big", signed=False), @@ -138,7 +141,7 @@ def bottem_side_length_of_smallest_square_bottom_box(total: int, maxHeight: int) return math.ceil(math.sqrt(math.ceil(total / maxHeight))) -def to_BDX_bytes( +def commands_to_BDX_bytes( commands: list, max_height: int = 64, ): @@ -178,7 +181,7 @@ def to_BDX_bytes( else (3 if z_forward else 2) if ( ((now_z != 0) and (not z_forward)) - or (z_forward and (now_z != _sideLength)) + or (z_forward and (now_z != _sideLength - 1)) ) else 5, impluse=impluse, @@ -204,15 +207,13 @@ def to_BDX_bytes( ): now_z -= 1 if z_forward else -1 z_forward = not z_forward - _bytes += key[x][1] + _bytes += bdx_key[x][1] now_x += 1 else: - - _bytes += key[z][int(z_forward)] + _bytes += bdx_key[z][int(z_forward)] else: - - _bytes += key[y][int(y_forward)] + _bytes += bdx_key[y][int(y_forward)] return ( _bytes, @@ -225,6 +226,60 @@ def to_BDX_bytes( ) +def form_note_block_in_NBT_struct( + note: int, coordinate: tuple, instrument: str = "note.harp", powered: bool = False +): + """生成音符盒方块 + :param note: `int`(0~24) + 音符的音高 + :param coordinate: `tuple[int,int,int]` + 此方块所在之相对坐标 + :param instrument: `str` + 音符盒的乐器 + :param powered: `bool` + 是否已被激活 + :return Block + """ + + return Block( + "minecraft", + "noteblock", + { + "instrument": instrument.replace("note.", ""), + "note": note, + "powered": powered, + }, + { + "block_entity_data": { + "note": TAG_Byte(note), + "id": "noteblock", + "x": coordinate[0], + "y": coordinate[1], + "z": coordinate[2], + } + }, + ) + + +def form_repeater_in_NBT_struct( + delay: int, facing: int +): + """生成中继器方块 + :param powered: + :param locked: + :param facing: + :param delay: 1~4 + :return Block()""" + + return Block( + "minecraft", + "unpowered_repeater", + { + "repeater_delay": delay, + "direction": facing, + }, + ) + def form_command_block_in_NBT_struct( command: str, coordinate: tuple, @@ -281,42 +336,43 @@ def form_command_block_in_NBT_struct( :return:str """ - from TrimMCStruct import Block, TAG_Long - block = Block( + + return Block( "minecraft", - "command_block" if impluse == 0 else ( - "repeating_command_block" if impluse == 1 else "chain_command_block"), - states={"conditional_bit": condition, - "facing_direction": particularValue}, + "command_block" + if impluse == 0 + else ("repeating_command_block" if impluse == 1 else "chain_command_block"), + states={"conditional_bit": condition, "facing_direction": particularValue}, extra_data={ - 'Command': command, - 'CustomName': customName, - 'ExecuteOnFirstTick': executeOnFirstTick, - 'LPCommandMode': 0, - 'LPCondionalMode': False, - 'LPRedstoneMode': False, - 'LastExecution': TAG_Long(0), - 'LastOutput': '', - 'LastOutputParams': [], - 'SuccessCount': 0, - 'TickDelay': tickDelay, - 'TrackOutput': trackOutput, - 'Version': 25, - 'auto': alwaysRun, - 'conditionMet': False, # 是否已经满足条件 - 'conditionalMode': condition, - 'id': 'CommandBlock', - 'isMovable': True, - 'powered': False, # 是否已激活 - 'x': coordinate[0], - 'y': coordinate[1], - 'z': coordinate[2], - } + "block_entity_data": { + "Command": command, + "CustomName": customName, + "ExecuteOnFirstTick": executeOnFirstTick, + "LPCommandMode": 0, + "LPCondionalMode": False, + "LPRedstoneMode": False, + "LastExecution": TAG_Long(0), + "LastOutput": "", + "LastOutputParams": [], + "SuccessCount": 0, + "TickDelay": tickDelay, + "TrackOutput": trackOutput, + "Version": 25, + "auto": alwaysRun, + "conditionMet": False, # 是否已经满足条件 + "conditionalMode": condition, + "id": "CommandBlock", + "isMovable": True, + "powered": False, # 是否已激活 + "x": coordinate[0], + "y": coordinate[1], + "z": coordinate[2], + } + }, + compability_version=17959425, ) - return block - -def to_structure( +def commands_to_structure( commands: list, max_height: int = 64, ): @@ -325,8 +381,6 @@ def to_structure( :param max_height: 生成结构最大高度 :return 成功与否,成功返回(结构类,结构占用大小),失败返回(False,str失败原因) """ - # 导入库 - from TrimMCStruct import Structure _sideLength = bottem_side_length_of_smallest_square_bottom_box( len(commands), max_height @@ -345,29 +399,33 @@ def to_structure( for cmd, delay in commands: coordinate = (now_x, now_y, now_z) - struct.set_block(coordinate, - form_command_block_in_NBT_struct( - command=cmd, - coordinate=coordinate, - particularValue=(1 if y_forward else 0) - if ( - ((now_y != 0) and (not y_forward)) - or (y_forward and (now_y != (max_height - 1))) - ) - else (3 if z_forward else 2) - if ( - ((now_z != 0) and (not z_forward)) - or (z_forward and (now_z != _sideLength)) - ) - else 5, - impluse=2, - condition=False, - alwaysRun=True, - tickDelay=delay, - customName="", - executeOnFirstTick=False, - trackOutput=True, - )) + struct.set_block( + coordinate, + form_command_block_in_NBT_struct( + command=cmd, + coordinate=coordinate, + particularValue=(1 if y_forward else 0) + if ( + ((now_y != 0) and (not y_forward)) + or (y_forward and (now_y != (max_height - 1))) + ) + else ( + (3 if z_forward else 2) + if ( + ((now_z != 0) and (not z_forward)) + or (z_forward and (now_z != _sideLength - 1)) + ) + else 5 + ), + impluse=2, + condition=False, + alwaysRun=True, + tickDelay=delay, + customName="", + executeOnFirstTick=False, + trackOutput=True, + ), + ) now_y += 1 if y_forward else -1 @@ -387,9 +445,11 @@ def to_structure( return ( struct, - [ + ( now_x + 1, max_height if now_x or now_z else now_y, _sideLength if now_x else now_z, - ], + ), + (now_x, now_y, now_z), ) + diff --git a/README.md b/README.md index 9378849..26b95fd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

- +

diff --git a/README_EN.md b/README_EN.md index 7b6ff90..2a0d106 100644 --- a/README_EN.md +++ b/README_EN.md @@ -4,7 +4,7 @@

-

a free open-source library of converting midi files into *Minecraft* formats.

+

a free open-source library of converting midi files into _Minecraft_ formats.

diff --git a/docs/库的生成与功能文档.md b/docs/库的生成与功能文档.md index 685ba25..266b53f 100644 --- a/docs/库的生成与功能文档.md +++ b/docs/库的生成与功能文档.md @@ -15,8 +15,10 @@ ```python import Musicreater # 导入转换库 +old_execute_format = False # 指定是否使用旧的execute指令语法(即1.18及以前的《我的世界:基岩版》语法) + # 首先新建转换对象。 -conversion = Musicreater.midiConvert() +conversion = Musicreater.midiConvert(enable_old_exe_format = old_execute_format) # 值得注意的是,一个转换对象可以转换多个文件。 # 也就是在实例化的时候不进行对文件的绑定。 # 如果有调试需要,可以在实例化时传入参数 debug = True @@ -26,10 +28,9 @@ conversion = Musicreater.midiConvert() # 地址都为字符串类型,不能传入文件流 midi_path = "./where/you/place/.midi/files.mid" output_folder = "./where/you/want2/convert/into/" -old_execute_format = False # 指定是否使用旧的execute指令语法(即1.18及以前的《我的世界:基岩版》语法) # 设定基本转换参数 -conversion.convert(midi_path,output_folder,old_execute_format) +conversion.convert(midi_path,output_folder) # 进行转换并接受输出,具体的参数均在代码之文档中有相关说明 method_id = 3 # 指定使用的转换算法 @@ -92,13 +93,13 @@ print(convertion_result) 考虑到进行《我的世界》游戏开发时,为了节约常加载区域,很多游戏会将指令区设立为一种层叠式的结构。这种结构会限制每一层的指令系统的高度,但是虽然长宽也是有限的,却仍然比其纵轴延伸得更加自由。 - 所以,结构的生成形状依照给定的高度和内含指令的数量决定。其$Z$轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整。用数学公式的方式表达,则是: + 所以,结构的生成形状依照给定的高度和内含指令的数量决定。其 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整。用数学公式的方式表达,则是: $$MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor$$ 其中,$MaxZ$即生成结构的$Z$轴最大延伸长度,$NoC$表示链结构中所含指令方块的个数,$MaxH$表示给定的生成结构的最大高度。 - 我们的结构生成器在生成指令链时,将首先以相对坐标系$(0, 0, 0)$(即相对原点)开始,自下向上堆叠高度轴(即$Y$轴)的长,当高度轴达到了限制的高度时,便将$Z$轴向正方向堆叠`1`个方块,并开始自上向下重新堆叠,直至高度轴坐标达到相对为`0`。若当所生成结构的$Z$轴长达到了其最大延伸长度,则此结构生成器将反转$Z$轴的堆叠方向,直至$Z$轴坐标相对为`0`。如此往复,直至指令链堆叠完成。 + 我们的结构生成器在生成指令链时,将首先以相对坐标系 $(0, 0, 0)$ (即相对原点)开始,自下向上堆叠高度轴(即 $Y$ 轴)的长,当高度轴达到了限制的高度时,便将 $Z$ 轴向正方向堆叠`1`个方块,并开始自上向下重新堆叠,直至高度轴坐标达到相对为`0`。若当所生成结构的 $Z$ 轴长达到了其最大延伸长度,则此结构生成器将反转 $Z$ 轴的堆叠方向,直至 $Z$ 轴坐标相对为`0`。如此往复,直至指令链堆叠完成。 ## 播放器 @@ -121,9 +122,9 @@ print(convertion_result) |`ScBd`|指定的计分板名称|| |`x`|音发出时对应的分数值|| |`InstID`|声音效果ID|不同的声音ID可以对应不同的乐器,详见转换[乐器对照表](./%E8%BD%AC%E6%8D%A2%E4%B9%90%E5%99%A8%E5%AF%B9%E7%85%A7%E8%A1%A8.md)| - |`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以$S$表示此参数`Ht`,以Vol表示音量百分比,则计算公式为:$S = \frac{1}{Vol}-1$| + |`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以 $S$ 表示此参数`Ht`,以Vol表示音量百分比,则计算公式为: $S = \frac{1}{Vol}-1$ | |`Vlct`|原生我的世界中规定的播放力度|这个参数是一个谜一样的存在,似乎它的值毫不重要……因为无论这个值是多少,我们听起来都差不多。当此音符所在MIDI通道为第一通道,则这个值为0.7倍MIDI指定力度,其他则为0.9倍。| - |`Ptc`|音符的音高|这是决定音调的参数。以$P$表示此参数,$n$表示其在MIDI中的编号,$x$表示一定的音域偏移,则计算公式为:$P = 2^\frac{n-60-x}{12}$| + |`Ptc`|音符的音高|这是决定音调的参数。以 $P$ 表示此参数, $n$ 表示其在MIDI中的编号, $x$ 表示一定的音域偏移,则计算公式为: $P = 2^\frac{n-60-x}{12}$ | 后四个参数决定了这个音的性质,而前两个参数仅仅是为了决定音播放的时间。 @@ -141,9 +142,9 @@ print(convertion_result) |--------|-----------|----------| |`Tg`|播放对象|选择器或玩家名| |`InstID`|声音效果ID|不同的声音ID可以对应不同的乐器,详见转换[乐器对照表](./%E8%BD%AC%E6%8D%A2%E4%B9%90%E5%99%A8%E5%AF%B9%E7%85%A7%E8%A1%A8.md)| - |`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以$S$表示此参数`Ht`,以Vol表示音量百分比,则计算公式为:$S = \frac{1}{Vol}-1$| + |`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以 $S$ 表示此参数`Ht`,以Vol表示音量百分比,则计算公式为: $S = \frac{1}{Vol}-1$ | |`Vlct`|原生我的世界中规定的播放力度|这个参数是一个谜一样的存在,似乎它的值毫不重要……因为无论这个值是多少,我们听起来都差不多。当此音符所在MIDI通道为第一通道,则这个值为0.7倍MIDI指定力度,其他则为0.9倍。| - |`Ptc`|音符的音高|这是决定音调的参数。以$P$表示此参数,$n$表示其在MIDI中的编号,$x$表示一定的音域偏移,则计算公式为:$P = 2^\frac{n-60-x}{12}$| + |`Ptc`|音符的音高|这是决定音调的参数。以 $P$ 表示此参数, $n$ 表示其在MIDI中的编号,$x$表示一定的音域偏移,则计算公式为: $P = 2^\frac{n-60-x}{12}$ | 其中后四个参数决定了这个音的性质。 diff --git a/example.py b/example.py index dd634b9..b51e155 100644 --- a/example.py +++ b/example.py @@ -20,8 +20,6 @@ import os import Musicreater - - # 获取midi列表 midi_path = input(f"请输入MIDI路径:") @@ -46,9 +44,6 @@ fileFormat = int(input(f"请输入输出格式[BDX(1) 或 MCPACK(0)]:").lower( playerFormat = int(input(f"请选择播放方式[计分板(1) 或 延迟(0)]:").lower()) - - - # 真假字符串判断 def bool_str(sth: str) -> bool: try: @@ -61,6 +56,7 @@ def bool_str(sth: str) -> bool: else: raise ValueError("布尔字符串啊?") + debug = False if os.path.exists("./demo_config.json"): @@ -75,40 +71,40 @@ else: # 提示语 检测函数 错误提示语 for args in [ ( - f'输入音量:', + f"输入音量:", float, ), ( - f'输入播放速度:', + f"输入播放速度:", float, ), ( - f'是否启用进度条:', + f"是否启用进度条:", bool_str, ), ( - f'计分板名称:', + f"计分板名称:", str, ) if playerFormat == 1 else ( - f'玩家选择器:', + f"玩家选择器:", str, ), ( - f'是否自动重置计分板:', + f"是否自动重置计分板:", bool_str, ) if playerFormat == 1 else (), ( - f'作者名称:', + f"作者名称:", str, ) if fileFormat == 1 else (), ( - f'最大结构高度:', + f"最大结构高度:", int, ) if fileFormat == 1 @@ -117,7 +113,7 @@ else: if args: prompts.append(args[1](input(args[0]))) -conversion = Musicreater.midiConvert(debug) +conversion = Musicreater.midiConvert(debug=debug) print(f"正在处理 {midi_path} :") diff --git a/example_mcstructure.py b/example_mcstructure.py index 3f8af86..b6407e6 100644 --- a/example_mcstructure.py +++ b/example_mcstructure.py @@ -1,6 +1,8 @@ - from Musicreater import midiConvert -conversion = midiConvert() -conversion.convert(input("midi path:"),input("out path:")) -conversion.to_mcstructure_file_with_delay(3,) \ No newline at end of file +conversion = midiConvert(enable_old_exe_format=False) +conversion.convert(input("midi路径:"), input("输出路径:")) + +conversion.to_mcstructure_file_with_delay( + 3, +) diff --git a/requirements.txt b/requirements.txt index b7afb8f..dfc8b0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ Brotli>=1.0.9 mido>=1.2.10 -TrimMCStruct>=0.0.5.5 \ No newline at end of file +TrimMCStruct>=0.0.5.6 \ No newline at end of file