diff --git a/LICENSE.md b/LICENSE.md index c8abe70..1a5a125 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -3,7 +3,7 @@ 1. 音·创的全部开发者享有其完整版权,其开发者可以在任一时刻终止以后音·创源代码开放,若经由其开发者授予特殊权利,则授权对象可以将源代码进行特定的被特殊授权的操作 2. 音·创或(及)其代码允许在 Apache2.0 协议的条款与说明下进行非商业使用 3. 除部分代码特殊声明外,音·创允许对其或(及)其代码进行商业化使用,但是需要经过音·创主要开发者(诸葛亮与八卦阵、金羿)的一致授权,同时,授权对象在商业化授权的使用过程中必须依照 Apache2.0 协议的条款与说明 -4. 若存在对于音·创包含的部分代码的特殊开源声明,则此部分代码依照其特定的开源方式授权,但若此部分代码经由此部分代码的主要开发者一致特殊授权后商用,则授权对象在商用时依照此部分的开发者所准许的方式(或条款)进行商用,或默认依照 Apache2.0 协议进行商业化使用 +4. 若存在对于音·创包含的部分代码的特殊开源声明,则此部分代码依照其特定的开源方式授权,但若此部分代码经由此部分代码的主要开发者一致特殊授权后商用,则授权对象在商用时依照此部分的开发者所准许的方式(或条款)进行商用 5. Apache2.0 协议的英文原文副本可见下文 > The English Translation of the TERMS AND CONDITIONS above is listed below @@ -203,7 +203,7 @@ END OF TERMS AND CONDITIONS - Copyright 2022 Team-Ryoun 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") + Copyright 2022 TriM-Organization 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 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. diff --git a/Musicreater/__init__.py b/Musicreater/__init__.py index 15ae47e..465face 100644 --- a/Musicreater/__init__.py +++ b/Musicreater/__init__.py @@ -8,17 +8,16 @@ A free open source library used for convert midi file into formats that is suita 版权所有 © 2023 音·创 开发者 Copyright © 2023 all the developers of Musicreater -开源相关声明请见 ../License.md -Terms & Conditions: ../License.md +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory """ # 睿穆组织 开发交流群 861684859 # Email TriM-Organization@hotmail.com -# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon") # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md from .main import * -__version__ = "0.5.1.1" +__version__ = "1.0.0" __all__ = [] __author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon")) diff --git a/Musicreater/constants.py b/Musicreater/constants.py index 7bb6b27..b89e0ee 100644 --- a/Musicreater/constants.py +++ b/Musicreater/constants.py @@ -1,7 +1,21 @@ +# -*- coding: utf-8 -*- + """ 存放常量与数值性内容 """ +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + x = "x" """ diff --git a/Musicreater/exceptions.py b/Musicreater/exceptions.py index 49e2136..1d62381 100644 --- a/Musicreater/exceptions.py +++ b/Musicreater/exceptions.py @@ -1,25 +1,23 @@ # -*- coding: utf-8 -*- +""" +存放一堆报错类型 +""" -# 睿穆组织 开发交流群 861684859 -# Email TriM-Organization@hotmail.com -# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon") -# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md - - -"""一个简单的我的世界音频转换库 -音·创 (Musicreater) -是一款免费开源的针对《我的世界》的midi音乐转换库 -Musicreater(音·创) -A free open source library used for convert midi file into formats that is suitable for **Minecraft**. - +""" 版权所有 © 2023 音·创 开发者 Copyright © 2023 all the developers of Musicreater -开源相关声明请见 ../License.md -Terms & Conditions: ../License.md +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory """ +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + + class MSCTBaseException(Exception): """音·创库版本的所有错误均继承于此""" diff --git a/Musicreater/experiment.py b/Musicreater/experiment.py index a41da8e..46a05ec 100644 --- a/Musicreater/experiment.py +++ b/Musicreater/experiment.py @@ -1,18 +1,32 @@ -''' -新版本功能以及即将启用的函数 -''' +# -*- coding: utf-8 -*- +""" +新版本功能以及即将启用的函数 +""" + + +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md from .exceptions import * +from .main import MidiConvert, mido from .subclass import * from .utils import * -from .main import midiConvert, mido # 简单的单音填充 def _toCmdList_m4( - self: midiConvert, + self: MidiConvert, scoreboard_name: str = "mscplay", MaxVolume: float = 1.0, speed: float = 1.0, @@ -58,9 +72,7 @@ def _toCmdList_m4( pass if msg.type == "program_change": - channels[msg.channel].append( - ("PgmC", msg.program, microseconds) - ) + channels[msg.channel].append(("PgmC", msg.program, microseconds)) elif msg.type == "note_on" and msg.velocity != 0: channels[msg.channel].append( @@ -193,7 +205,6 @@ def _toCmdList_m4( return [tracks, cmdAmount, maxScore] - def to_note_list( self, speed: float = 1.0, @@ -227,9 +238,7 @@ def to_note_list( 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.debug_mode: @@ -347,12 +356,10 @@ def to_note_list( 0 if j != 0 else ( - all_ticks[i] - all_ticks[i - 1] - if i != 0 - else all_ticks[i] + all_ticks[i] - all_ticks[i - 1] if i != 0 else all_ticks[i] ) ), ) ) - return [results, max(all_ticks)] \ No newline at end of file + return [results, max(all_ticks)] diff --git a/Musicreater/main.py b/Musicreater/main.py index 22d7d94..b308278 100644 --- a/Musicreater/main.py +++ b/Musicreater/main.py @@ -1,12 +1,6 @@ # -*- coding: utf-8 -*- -# 音·创 开发交流群 861684859 -# Email TriM-Organization@hotmail.com -# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon") -# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md - - """ 音·创 (Musicreater) 是一款免费开源的针对《我的世界》的midi音乐转换库 @@ -16,16 +10,18 @@ A free open source library used for convert midi file into formats that is suita 版权所有 © 2023 音·创 开发者 Copyright © 2023 all the developers of Musicreater -开源相关声明请见 ../License.md -Terms & Conditions: ../License.md +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory """ +# 音·创 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md + + import os import math -import json -import shutil -import uuid -from typing import TypeVar, Union, Tuple +from typing import Tuple, List import mido @@ -35,9 +31,9 @@ from .utils import * from .subclass import * VM = TypeVar("VM", mido.MidiFile, None) # void mido -''' +""" 空Midi类类型 -''' +""" """ 学习笔记: @@ -73,69 +69,98 @@ tick * tempo / 1000000.0 / ticks_per_beat * 一秒多少游戏刻 """ -class midiConvert: - def __init__(self, enable_old_exe_format: bool = False, debug: bool = False): +@dataclass(init=False) +class MidiConvert: + """ + 将Midi文件转换为我的世界内容 + """ + + midi: VM + """MidiFile对象""" + + midi_music_name: str + """Midi乐曲名""" + + enable_old_exe_format: bool + """是否启用旧版execute指令格式""" + + execute_cmd_head: str + """execute指令头部""" + + music_command_list: List[SingleCommand] + """音乐指令列表""" + + music_tick_num: int + """音乐总延迟""" + + progress_bar_command: List[SingleCommand] + """进度条指令列表""" + + def __init__( + self, + midi_obj: VM, + midi_name: str, + enable_old_exe_format: bool = False, + ): """ - 简单的midi转换类,将midi文件转换为我的世界结构或者包 - + 简单的midi转换类,将midi对象转换为我的世界结构或者包 + Parameters ---------- + midi_obj: mido.MidiFile 对象 + 需要处理的midi对象 + midi_name: MIDI乐曲名称 + 此音乐之名 enable_old_exe_format: bool 是否启用旧版(≤1.19)指令格式,默认为否 - debug: bool - 是否启用调试模式,默认为否 """ - self.debug_mode: bool = debug - """是否开启调试模式""" + self.midi: VM = midi_obj - self.midi_file: str = "" - """Midi文件路径""" + self.midi_music_name: str = midi_name - self.midi: VM = None - """MidiFile对象""" - - self.output_path: str = "" - """输出路径""" - - self.mid_file_name: str = "" - """文件名,不含路径且不含后缀""" - - self.execute_cmd_head = "" - """execute 指令的执行开头,用于被format""" - - self.enable_old_exe_format = enable_old_exe_format - """是否启用旧版指令格式""" + self.enable_old_exe_format: bool = enable_old_exe_format self.execute_cmd_head = ( "execute {} ~ ~ ~ " if enable_old_exe_format else "execute as {} at @s positioned ~ ~ ~ run " ) - """execute指令头部""" - def convert(self, midi_file: str, output_path: str): - """转换前需要先运行此函数来获取基本信息""" + self.progress_bar_command = self.music_command_list = [] + self.music_tick_num = 0 - self.midi_file = midi_file - """midi文件路径""" + @classmethod + def from_midi_file( + cls, + midi_file_path: str, + old_exe_format: bool = False, + ): + """ + 直接输入文件地址,将midi文件读入 + + Parameters + ---------- + midi_file: str + midi文件地址 + enable_old_exe_format: bool + 是否启用旧版(≤1.19)指令格式,默认为否 + """ + + midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0] + """文件名,不含路径且不含后缀""" 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] - """文件名,不含路径且不含后缀""" + return cls(mido.MidiFile(midi_file_path), midi_music_name, old_exe_format) + except (ValueError, TypeError) as E: + raise MidiDestroyedError(f"文件{midi_file_path}损坏:{E}") + except FileNotFoundError as E: + raise FileNotFoundError(f"文件{midi_file_path}不存在:{E}") @staticmethod def inst_to_souldID_withX( instrumentID: int, - ): + ) -> Tuple[str, int]: """ 返回midi的乐器ID对应的我的世界乐器名,对于音域转换算法,如下: 2**( ( msg.note - 60 - X ) / 12 ) 即为MC的音高,其中 @@ -162,7 +187,7 @@ class midiConvert: return "note.flute", 5 @staticmethod - def perc_inst_to_soundID_withX(instrumentID: int): + def perc_inst_to_soundID_withX(instrumentID: int) -> Tuple[str, int]: """ 对于Midi第10通道所对应的打击乐器,返回我的世界乐器名 @@ -193,7 +218,7 @@ class midiConvert: max_score: int, scoreboard_name: str, progressbar_style: tuple = DEFAULT_PROGRESSBAR_STYLE, - ) -> list: + ) -> List[SingleCommand]: """ 生成进度条 @@ -210,7 +235,7 @@ class midiConvert: Returns ------- - list[str"指令",] + list[SingleCommand,] """ pgs_style = progressbar_style[0] """用于被替换的进度条原始样式""" @@ -227,101 +252,165 @@ class midiConvert: | `_` | 用以表示进度条占位| """ perEach = max_score / pgs_style.count("_") - '''每个进度条代表的分值''' + """每个进度条代表的分值""" - result = [] + result: List[SingleCommand] = [] 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", self.mctick2timestr(max_score)) + pgs_style = pgs_style.replace(r"%^t", mctick2timestr(max_score)) sbn_pc = scoreboard_name[:2] if r"%%%" in pgs_style: result.append( - 'scoreboard objectives add {}PercT dummy "百分比计算"'.format(sbn_pc) - ) - result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players set MaxScore {} {}".format( - scoreboard_name, max_score + SingleCommand( + 'scoreboard objectives add {}PercT dummy "百分比计算"'.format(sbn_pc), + annotation="新增临时计算用计分板(百分比)", ) ) result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players set n100 {} 100".format(scoreboard_name) - ) - result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players operation @s {} = @s {}".format( - sbn_pc + "PercT", scoreboard_name + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players set MaxScore {} {}".format( + scoreboard_name, max_score + ), + annotation="设定此音乐最大计分", ) ) result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players operation @s {} *= n100 {}".format( - sbn_pc + "PercT", scoreboard_name + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players set n100 {} 100".format(scoreboard_name), + annotation="设置常量100", ) ) result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players operation @s {} /= MaxScore {}".format( - sbn_pc + "PercT", scoreboard_name + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} = @s {}".format( + sbn_pc + "PercT", scoreboard_name + ), + annotation="为临时变量赋值", + ) + ) + result.append( + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} *= n100 {}".format( + sbn_pc + "PercT", scoreboard_name + ), + annotation="改变临时变量的单位为百分比(扩大精度)", + ) + ) + result.append( + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} /= MaxScore {}".format( + sbn_pc + "PercT", scoreboard_name + ), + annotation="使用临时变量计算百分比", ) ) if r"%%t" in pgs_style: result.append( - 'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format(sbn_pc) - ) - result.append( - 'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(sbn_pc) - ) - result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players set n20 {} 20".format(scoreboard_name) - ) - result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players set n60 {} 60".format(scoreboard_name) - ) - - result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players operation @s {} = @s {}".format( - sbn_pc + "TMinT", scoreboard_name + SingleCommand( + 'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format(sbn_pc), + annotation="新增临时计算计分板(分)", ) ) result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players operation @s {} /= n20 {}".format( - sbn_pc + "TMinT", scoreboard_name + SingleCommand( + 'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(sbn_pc), + annotation="新增临时计算计分板(秒)", ) ) result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players operation @s {} /= n60 {}".format( - sbn_pc + "TMinT", scoreboard_name + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players set n20 {} 20".format(scoreboard_name), + annotation="设置常量20", + ) + ) + result.append( + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players set n60 {} 60".format(scoreboard_name), + annotation="设置常量60", ) ) result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players operation @s {} = @s {}".format( - sbn_pc + "TSecT", scoreboard_name + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} = @s {}".format( + sbn_pc + "TMinT", scoreboard_name + ), + annotation="为临时变量(分)赋值", ) ) result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players operation @s {} /= n20 {}".format( - sbn_pc + "TSecT", scoreboard_name + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} /= n20 {}".format( + sbn_pc + "TMinT", scoreboard_name + ), + annotation="将临时变量转换单位为秒(缩减精度)", ) ) result.append( - self.execute_cmd_head.format("@a[scores={" + scoreboard_name + "=1..}]") - + "scoreboard players operation @s {} %= n60 {}".format( - sbn_pc + "TSecT", scoreboard_name + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} = @s {}".format( + sbn_pc + "TSecT", sbn_pc + "TMinT" + ), + annotation="为临时变量(秒)赋值", + ) + ) + + result.append( + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} /= n60 {}".format( + sbn_pc + "TMinT", scoreboard_name + ), + annotation="将临时变量(分)转换单位为分(缩减精度)", + ) + ) + + result.append( + SingleCommand( + self.execute_cmd_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} %= n60 {}".format( + sbn_pc + "TSecT", scoreboard_name + ), + annotation="将临时变量(秒)确定下来(框定精度区间)", ) ) @@ -329,7 +418,7 @@ class midiConvert: npg_stl = ( pgs_style.replace("_", progressbar_style[1][0], i + 1) .replace("_", progressbar_style[1][1]) - .replace(r"%%N", self.mid_file_name) + .replace(r"%%N", self.midi_music_name) if r"%%N" in pgs_style else pgs_style.replace("_", progressbar_style[1][0], i + 1).replace( "_", progressbar_style[1][1] @@ -358,31 +447,50 @@ class midiConvert: ), ) result.append( - self.execute_cmd_head.format( - r"@a[scores={" - + scoreboard_name - + f"={int(i * perEach)}..{math.ceil((i + 1) * perEach)}" - + r"}]" + SingleCommand( + self.execute_cmd_head.format( + r"@a[scores={" + + scoreboard_name + + f"={int(i * perEach)}..{math.ceil((i + 1) * perEach)}" + + r"}]" + ) + + r'titleraw @s actionbar {"rawtext":[{"text":"' + + npg_stl + + r'"}]}', + annotation="进度条显示", ) - + r'titleraw @s actionbar {"rawtext":[{"text":"' - + npg_stl - + r'"}]}' ) if r"%%%" in pgs_style: - result.append("scoreboard objectives remove {}PercT".format(sbn_pc)) + result.append( + SingleCommand( + "scoreboard objectives remove {}PercT".format(sbn_pc), + annotation="移除临时计算计分板(百分比)", + ) + ) if r"%%t" in pgs_style: - result.append("scoreboard objectives remove {}TMinT".format(sbn_pc)) - result.append("scoreboard objectives remove {}TSecT".format(sbn_pc)) + result.append( + SingleCommand( + "scoreboard objectives remove {}TMinT".format(sbn_pc), + annotation="移除临时计算计分板(分)", + ) + ) + result.append( + SingleCommand( + "scoreboard objectives remove {}TSecT".format(sbn_pc), + annotation="移除临时计算计分板(秒)", + ) + ) + self.progress_bar_command = result return result - def to_command_list( + def to_command_list_in_score( self, scoreboard_name: str = "mscplay", max_volume: float = 1.0, speed: float = 1.0, - ) -> list: + ) -> Tuple[List[List[SingleCommand]], int, int]: """ 使用金羿的转换思路,将midi转换为我的世界命令列表 @@ -397,13 +505,11 @@ class midiConvert: Returns ------- - tuple( list[list[str指令,... ],... ], int指令数量, int最大计分 ) + tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 ) """ if speed == 0: - if self.debug_mode: - raise ZeroSpeedError("播放速度仅可为正实数") - speed = 1 + raise ZeroSpeedError("播放速度仅可为正实数") max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume) # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 @@ -422,27 +528,23 @@ class midiConvert: ) # print(microseconds) except NameError: - if self.debug_mode: - raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") - else: - microseconds += ( - msg.time - * mido.midifiles.midifiles.DEFAULT_TEMPO - / self.midi.ticks_per_beat - ) / 1000 + # raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") + 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.debug_mode: - self.prt(f"TEMPO更改:{tempo}(毫秒每拍)") else: - if self.debug_mode: - try: - if msg.channel > 15: - raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") - except AttributeError: - pass + # 曾用于调试模式 + # 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] = [] @@ -505,41 +607,47 @@ class midiConvert: else self.inst_to_souldID_withX(InstID) ) except UnboundLocalError as E: - if self.debug_mode: - raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") - else: - soundID, _X = ( - self.perc_inst_to_soundID_withX(-1) - if SpecialBits - else self.inst_to_souldID_withX(-1) - ) + # raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") + soundID, _X = ( + self.perc_inst_to_soundID_withX(-1) + if SpecialBits + else self.inst_to_souldID_withX(-1) + ) score_now = round(msg[-1] / float(speed) / 50) maxScore = max(maxScore, score_now) nowTrack.append( - self.execute_cmd_head.format( - "@a[scores=({}={})]".format(scoreboard_name, score_now) - .replace("(", r"{") - .replace(")", r"}") - ) - + f"playsound {soundID} @s ^ ^ ^{1 / max_volume - 1} {msg[2] / 128} " - f"{2 ** ((msg[1] - 60 - _X) / 12)}" + SingleCommand( + self.execute_cmd_head.format( + "@a[scores=({}={})]".format( + scoreboard_name, score_now + ) + .replace("(", r"{") + .replace(")", r"}") + ) + + f"playsound {soundID} @s ^ ^ ^{1 / max_volume - 1} {msg[2] / 128} " + f"{2 ** ((msg[1] - 60 - _X) / 12)}", + annotation="在{}播放{}%的{}音".format( + mctick2timestr(score_now), max_volume * 100, "" + ), + ), ) cmdAmount += 1 if nowTrack: + self.music_command_list.extend(nowTrack) tracks.append(nowTrack) - return [tracks, cmdAmount, maxScore] + self.music_tick_num = maxScore + return (tracks, cmdAmount, maxScore) - - def to_command_list_with_delay( + def to_command_list_in_delay( self, max_volume: float = 1.0, speed: float = 1.0, player_selector: str = "@a", - ) -> list: + ) -> Tuple[List[SingleCommand], int]: """ 使用金羿的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟 @@ -554,13 +662,11 @@ class midiConvert: Returns ------- - tuple( list[tuple(str指令, int距离上一个指令的延迟 ),...], int音乐时长游戏刻 ) + tuple( list[SingleCommand,...], int音乐时长游戏刻 ) """ if speed == 0: - if self.debug_mode: - raise ZeroSpeedError("播放速度仅可为正实数") - speed = 1 + raise ZeroSpeedError("播放速度仅可为正实数") max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume) # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 @@ -579,24 +685,21 @@ class midiConvert: ) # print(microseconds) except NameError: - if self.debug_mode: - raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") - else: - microseconds += ( - msg.time - * mido.midifiles.midifiles.DEFAULT_TEMPO - / self.midi.ticks_per_beat - ) / 1000 + # raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") + 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.debug_mode: - self.prt(f"TEMPO更改:{tempo}(毫秒每拍)") else: try: - if msg.channel > 15 and self.debug_mode: - raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") + # 曾用于调试模式 + # if msg.channel > 15: + # raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") if not track_no in channels[msg.channel].keys(): channels[msg.channel][track_no] = [] except AttributeError: @@ -657,14 +760,12 @@ class midiConvert: else self.inst_to_souldID_withX(InstID) ) except UnboundLocalError as E: - if self.debug_mode: - raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") - else: - soundID, _X = ( - self.perc_inst_to_soundID_withX(-1) - if SpecialBits - else self.inst_to_souldID_withX(-1) - ) + # raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") + soundID, _X = ( + self.perc_inst_to_soundID_withX(-1) + if SpecialBits + else self.inst_to_souldID_withX(-1) + ) score_now = round(msg[-1] / float(speed) / 50) # print(score_now) @@ -688,9 +789,9 @@ class midiConvert: for i in range(len(all_ticks)): for j in range(len(tracks[all_ticks[i]])): results.append( - ( + SingleCommand( tracks[all_ticks[i]][j], - ( + tick_delay=( 0 if j != 0 else ( @@ -699,608 +800,18 @@ class midiConvert: else all_ticks[i] ) ), + annotation="在{}播放{}%的{}音".format( + mctick2timestr(i), max_volume * 100, "" + ), ) ) - return [results, max(all_ticks)] + self.music_command_list = results + self.music_tick_num = max(all_ticks) + return [results, self.music_tick_num] - def to_mcpack( - self, - volume: float = 1.0, - speed: float = 1.0, - progressbar: Union[bool, Tuple[str, Tuple[str,]]] = None, - scoreboard_name: str = "mscplay", - auto_reset: bool = False, - ) -> tuple: - """ - 将midi转换为我的世界mcpack格式的包 - - Parameters - ---------- - volume: float - 音量,注意:这里的音量范围为(0,1],其原理为在距离玩家 (1 / volume -1) 的地方播放音频 - speed: float - 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed - progressbar: bool|tuple[str, Tuple[str,]] - 进度条,当此参数为 `True` 时使用默认进度条,为其他的**值为真**的参数时识别为进度条自定义参数,为其他**值为假**的时候不生成进度条 - scoreboard_name: str - 我的世界的计分板名称 - auto_reset: bool - 是否自动重置计分板 - - Returns - ------- - tuple(int指令长度, int最大计分) - """ - - cmdlist, maxlen, maxscore = self.to_command_list(scoreboard_name, volume, speed) - - # 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目,然后创建 - 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.output_path}/temp/manifest.json"): - with open( - f"{self.output_path}/temp/manifest.json", "w", encoding="utf-8" - ) as f: - f.write( - '{\n "format_version": 1,\n "header": {\n "description": "' - + self.mid_file_name - + ' Pack : behavior pack",\n "version": [ 0, 0, 1 ],\n "name": "' - + self.mid_file_name - + 'Pack",\n "uuid": "' - + str(uuid.uuid4()) - + '"\n },\n "modules": [\n {\n "description": "' - + 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.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.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.output_path}/temp/manifest.json", "w", encoding="utf-8").write( - json.dumps(data) - ) - - # 将命令列表写入文件 - index_file = open( - 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.output_path}/temp/functions/mscplay/track{cmdlist.index(track) + 1}.mcfunction", - "w", - encoding="utf-8", - ) as f: - f.write("\n".join(track)) - index_file.writelines( - ( - "scoreboard players add @a[scores={" - + scoreboard_name - + "=1..}] " - + scoreboard_name - + " 1\n", - ( - "scoreboard players reset @a[scores={" - + scoreboard_name - + "=" - + str(maxscore + 20) - + "..}]" - + f" {scoreboard_name}\n" - ) - if auto_reset - else "", - f"function mscplay/progressShow\n" if progressbar else "", - ) - ) - - if progressbar: - # 此处是对于仅有 True 的参数和自定义参数的判断 - # 改这一行没🐎 - if progressbar is True: - with open( - f"{self.output_path}/temp/functions/mscplay/progressShow.mcfunction", - "w", - encoding="utf-8", - ) as f: - f.writelines( - "\n".join(self.form_progress_bar(maxscore, scoreboard_name)) - ) - else: - with open( - f"{self.output_path}/temp/functions/mscplay/progressShow.mcfunction", - "w", - encoding="utf-8", - ) as f: - f.writelines( - "\n".join( - self.form_progress_bar( - maxscore, scoreboard_name, progressbar - ) - ) - ) - - 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 maxlen, maxscore - - def to_mcpack_with_delay( - self, - method: int = 1, - volume: float = 1.0, - speed: float = 1.0, - progressbar: Union[bool, tuple] = False, - player: str = "@a", - max_height: int = 64, - ): - """ - 使用method指定的转换算法,将midi转换为mcstructure结构文件后打包成mcpack文件 - :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失败原因) - """ - - from TrimMCStruct import Structure - - if self.enable_old_exe_format: - raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。") - - command_list, max_delay = self.methods_byDelay[method - 1]( - volume, - speed, - player, - ) - - # 此处是对于仅有 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.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, - alwaysRun=False, - 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, - 0, - alwaysRun=False, - 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, - method: int = 1, - volume: float = 1.0, - speed: float = 1.0, - progressbar: Union[bool, tuple] = False, - scoreboard_name: str = "mscplay", - isAutoReset: bool = False, - author: str = "Eilles", - max_height: int = 64, - ): - """ - 使用method指定的转换算法,将midi转换为BDX结构文件 - :param method: 转换算法 - :param author: 作者名称 - :param progressbar: 进度条,(当此参数为True时使用默认进度条,为其他的值为真的参数时识别为进度条自定义参数,为其他值为假的时候不生成进度条) - :param max_height: 生成结构最大高度 - :param scoreboard_name: 我的世界的计分板名称 - :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 - :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed - :param isAutoReset: 是否自动重置计分板 - :return 成功与否,成功返回(True,未经过压缩的源,结构占用大小),失败返回(False,str失败原因) - """ - # try: - cmdlist, total_count, maxScore = self.methods[method - 1]( - scoreboard_name, volume, speed - ) - # except Exception as E: - # return (False, f"无法找到算法ID{method}对应的转换算法: {E}") - - if not os.path.exists(self.output_path): - os.makedirs(self.output_path) - - with open( - os.path.abspath( - os.path.join(self.output_path, f"{self.mid_file_name}.bdx") - ), - "w+", - ) as f: - f.write("BD@") - - _bytes = ( - b"BDX\x00" - + author.encode("utf-8") - + b" & Musicreater\x00\x01command_block\x00" - ) - - commands = [] - - for track in cmdlist: - commands += track - - if isAutoReset: - commands.append( - "scoreboard players reset @a[scores={" - + scoreboard_name - + "=" - + str(maxScore + 20) - + "}] " - + scoreboard_name, - ) - - cmdBytes, size, finalPos = commands_to_BDX_bytes( - [(i, 0) for i in commands], max_height - 1 - ) - - if progressbar: - pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes( - [ - (i, 0) - for i in ( - self.form_progress_bar(maxScore, scoreboard_name) - # 此处是对于仅有 True 的参数和自定义参数的判断 - # 改这一行没🐎 - if progressbar is True - else self.form_progress_bar( - maxScore, scoreboard_name, progressbar - ) - ) - ], - max_height - 1, - ) - _bytes += pgbBytes - _bytes += bdx_move(y, -pgbNowPos[1]) - _bytes += bdx_move(z, -pgbNowPos[2]) - _bytes += bdx_move(x, 2) - - size[0] += 2 + pgbSize[0] - size[1] = max(size[1], pgbSize[1]) - size[2] = max(size[2], pgbSize[2]) - - _bytes += cmdBytes - - with open( - 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")) - - return True, total_count, maxScore, size, finalPos - - def to_BDX_file_with_delay( - self, - method: int = 1, - volume: float = 1.0, - speed: float = 1.0, - progressbar: Union[bool, tuple] = False, - player: str = "@a", - author: str = "Eilles", - max_height: int = 64, - ): - """ - 使用method指定的转换算法,将midi转换为BDX结构文件 - :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失败原因) - """ - - # try: - cmdlist, max_delay = self.methods_byDelay[method - 1]( - volume, - speed, - player, - ) - # except Exception as E: - # return (False, f"无法找到算法ID{method}对应的转换算法\n{E}") - - if not os.path.exists(self.output_path): - os.makedirs(self.output_path) - - with open( - os.path.abspath( - os.path.join(self.output_path, f"{self.mid_file_name}.bdx") - ), - "w+", - ) as f: - f.write("BD@") - - _bytes = ( - b"BDX\x00" - + author.encode("utf-8") - + b" & Musicreater\x00\x01command_block\x00" - ) - - # 此处是对于仅有 True 的参数和自定义参数的判断 - # 改这一行没🐎 - if progressbar is True: - progressbar = DEFAULT_PROGRESSBAR_STYLE - - cmdBytes, size, finalPos = commands_to_BDX_bytes(cmdlist, max_height - 1) - - if progressbar: - scb_name = self.mid_file_name[:5] + "Pgb" - _bytes += form_command_block_in_BDX_bytes( - r"scoreboard objectives add {} dummy {}计".replace(r"{}", scb_name), - 1, - customName="初始化进度条", - ) - _bytes += bdx_move(z, 2) - _bytes += form_command_block_in_BDX_bytes( - r"scoreboard players add {} {} 1".format(player, scb_name), - 1, - 1, - customName="显示进度条并加分", - ) - _bytes += bdx_move(y, 1) - pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes( - [ - (i, 0) - for i in self.form_progress_bar(max_delay, scb_name, progressbar) - ], - max_height - 1, - ) - _bytes += pgbBytes - _bytes += bdx_move(y, -1 - pgbNowPos[1]) - _bytes += bdx_move(z, -2 - pgbNowPos[2]) - _bytes += bdx_move(x, 2) - _bytes += form_command_block_in_BDX_bytes( - r"scoreboard players reset {} {}".format(player, scb_name), - 1, - customName="置零进度条", - ) - _bytes += bdx_move(y, 1) - size[0] += 2 + pgbSize[0] - size[1] = max(size[1], pgbSize[1]) - size[2] = max(size[2], pgbSize[2]) - - size[1] += 1 - _bytes += cmdBytes - - with open( - 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")) - - return True, len(cmdlist), max_delay, size, finalPos - - def toDICT( + def to_dict( self, ) -> dict: """ @@ -1308,26 +819,9 @@ class midiConvert: :return: dict() """ + # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 - channels = { - 0: {}, - 1: {}, - 2: {}, - 3: {}, - 4: {}, - 5: {}, - 6: {}, - 7: {}, - 8: {}, - 9: {}, - 10: {}, - 11: {}, - 12: {}, - 13: {}, - 14: {}, - 15: {}, - 16: {}, - } + channels = empty_midi_channels() # 我们来用通道统计音乐信息 # 但是是用分轨的思路的 @@ -1342,24 +836,21 @@ class midiConvert: ) # print(microseconds) except NameError: - if self.debug_mode: - raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") - else: - microseconds += ( - msg.time - * mido.midifiles.midifiles.DEFAULT_TEMPO - / self.midi.ticks_per_beat - ) / 1000 + # raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") + 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.debug_mode: - self.prt(f"TEMPO更改:{tempo}(毫秒每拍)") else: try: - if msg.channel > 15 and self.debug_mode: - raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") + # 曾用于调试模式 + # if msg.channel > 15: + # raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") if not track_no in channels[msg.channel].keys(): channels[msg.channel][track_no] = [] except AttributeError: @@ -1393,3 +884,18 @@ class midiConvert: 3 音符结束消息 ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)""" + + return channels + + + def copy_important(self): + dst = MidiConvert( + midi_obj=None, + midi_name=self.midi_music_name, + enable_old_exe_format=self.enable_old_exe_format, + ) + dst.music_command_list = [i.copy() for i in self.music_command_list] + dst.progress_bar_command = [i.copy() for i in self.progress_bar_command] + dst.music_tick_num = self.music_tick_num + return dst + diff --git a/Musicreater/plugin/__init__.py b/Musicreater/plugin/__init__.py index a07c08d..50d7b1c 100644 --- a/Musicreater/plugin/__init__.py +++ b/Musicreater/plugin/__init__.py @@ -5,15 +5,16 @@ 版权所有 © 2023 音·创 开发者 Copyright © 2023 all the developers of Musicreater -开源相关声明请见 ../../License.md -Terms & Conditions: ../../License.md +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory """ # 睿穆组织 开发交流群 861684859 # Email TriM-Organization@hotmail.com -# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon") # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md __all__ = [] -__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon")) \ No newline at end of file +__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray")) + +from .main import * \ No newline at end of file diff --git a/Musicreater/plugin/archive.py b/Musicreater/plugin/archive.py index 253b0f2..76ff3fe 100644 --- a/Musicreater/plugin/archive.py +++ b/Musicreater/plugin/archive.py @@ -1,8 +1,26 @@ +# -*- coding: utf-8 -*- +""" +存放关于文件打包的内容 +""" +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + import os +import uuid import zipfile +from typing import List def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None): @@ -23,4 +41,35 @@ def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None): pathfile = os.path.join(parent, filename) arc_name = pathfile[pre_len:].strip(os.path.sep) # 相对路径 zipf.write(pathfile, arc_name) - zipf.close() \ No newline at end of file + zipf.close() + + +def behavior_mcpack_manifest( + pack_description: str = "", + pack_version: List[int] = [0, 0, 1], + pack_name: str = "", + pack_uuid: str = None, + modules_description: str = "", + modules_version: List[int] = [0, 0, 1], + modules_uuid: str = None, +): + """ + 生成一个我的世界行为包组件的定义清单文件 + """ + return { + "format_version": 1, + "header": { + "description": pack_description, + "version": pack_version, + "name": pack_name, + "uuid": str(uuid.uuid4()) if not pack_uuid else pack_uuid, + }, + "modules": [ + { + "description": modules_description, + "type": "data", + "version": modules_version, + "uuid": str(uuid.uuid4()) if not modules_uuid else modules_uuid, + } + ], + } diff --git a/Musicreater/plugin/bdx.py b/Musicreater/plugin/bdx.py index a9d8ffd..b2d4f11 100644 --- a/Musicreater/plugin/bdx.py +++ b/Musicreater/plugin/bdx.py @@ -1,11 +1,25 @@ -''' +# -*- coding: utf-8 -*- +""" 存放有关BDX结构操作的内容 -''' +""" +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + +from typing import List + +from ..constants import x, y, z +from ..subclass import SingleCommand from .common import bottem_side_length_of_smallest_square_bottom_box -from ..constants import x,y,z - bdx_key = { "x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"], @@ -26,11 +40,11 @@ def bdx_move(axis: str, value: int): [ 1 if i else 0 for i in ( - value != -1, - value < -1 or value > 1, - value < -128 or value > 127, - value < -32768 or value > 32767, - ) + value != -1, + value < -1 or value > 1, + value < -128 or value > 127, + value < -32768 or value > 32767, + ) ] ) @@ -39,17 +53,16 @@ def bdx_move(axis: str, value: int): ) - def form_command_block_in_BDX_bytes( - command: str, - particularValue: int, - impluse: int = 0, - condition: bool = False, - needRedstone: bool = True, - tickDelay: int = 0, - customName: str = "", - executeOnFirstTick: bool = False, - trackOutput: bool = True, + command: str, + particularValue: int, + impluse: int = 0, + condition: bool = False, + needRedstone: bool = True, + tickDelay: int = 0, + customName: str = "", + executeOnFirstTick: bool = False, + trackOutput: bool = True, ): """ 使用指定项目返回指定的指令方块放置指令项 @@ -113,8 +126,8 @@ def form_command_block_in_BDX_bytes( def commands_to_BDX_bytes( - commands: list, - max_height: int = 64, + commands_list: List[SingleCommand], + max_height: int = 64, ): """ :param commands: 指令列表(指令, 延迟) @@ -123,7 +136,7 @@ def commands_to_BDX_bytes( """ _sideLength = bottem_side_length_of_smallest_square_bottom_box( - len(commands), max_height + len(commands_list), max_height ) _bytes = b"" @@ -134,34 +147,28 @@ def commands_to_BDX_bytes( now_z = 0 now_x = 0 - for cmd, delay in commands: - impluse = 2 - condition = False - needRedstone = False - tickDelay = delay - customName = "" - executeOnFirstTick = False - trackOutput = True + for command in commands_list: + _bytes += form_command_block_in_BDX_bytes( - cmd, + command.command_text, (1 if y_forward else 0) if ( - ((now_y != 0) and (not y_forward)) - or (y_forward and (now_y != (max_height - 1))) + ((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)) + ((now_z != 0) and (not z_forward)) + or (z_forward and (now_z != _sideLength - 1)) ) else 5, - impluse=impluse, - condition=condition, - needRedstone=needRedstone, - tickDelay=tickDelay, - customName=customName, - executeOnFirstTick=executeOnFirstTick, - trackOutput=trackOutput, + impluse=2, + condition=command.conditional, + needRedstone=False, + tickDelay=command.delay, + customName=command.annotation_text, + executeOnFirstTick=False, + trackOutput=True, ) now_y += 1 if y_forward else -1 @@ -174,7 +181,7 @@ def commands_to_BDX_bytes( now_z += 1 if z_forward else -1 if ((now_z >= _sideLength) and z_forward) or ( - (now_z < 0) and (not z_forward) + (now_z < 0) and (not z_forward) ): now_z -= 1 if z_forward else -1 z_forward = not z_forward @@ -195,5 +202,3 @@ def commands_to_BDX_bytes( ], [now_x, now_y, now_z], ) - - diff --git a/Musicreater/plugin/bdxfile/__init__.py b/Musicreater/plugin/bdxfile/__init__.py new file mode 100644 index 0000000..c59dcff --- /dev/null +++ b/Musicreater/plugin/bdxfile/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +""" +用以生成BDX结构文件的附加功能 + +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +__all__ = [] +__author__ = (("金羿", "Eilles Wan"),) + +from .main import * + diff --git a/Musicreater/plugin/bdxfile/main.py b/Musicreater/plugin/bdxfile/main.py new file mode 100644 index 0000000..a60d795 --- /dev/null +++ b/Musicreater/plugin/bdxfile/main.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +import os +import brotli + +from ...main import MidiConvert +from ..bdx import commands_to_BDX_bytes, bdx_move, x, y, z, form_command_block_in_BDX_bytes +from ..main import ConvertConfig +from ...subclass import SingleCommand + + +def to_BDX_file_in_score( + midi_cvt: MidiConvert, + data_cfg: ConvertConfig, + scoreboard_name: str = "mscplay", + auto_reset: bool = False, + author: str = "Eilles", + max_height: int = 64, +): + """ + 将midi以计分播放器形式转换为BDX结构文件 + + Parameters + ---------- + midi_cvt: MidiConvert 对象 + 用于转换的MidiConvert对象 + data_cfg: ConvertConfig 对象 + 部分转换通用参数 + scoreboard_name: str + 我的世界的计分板名称 + auto_reset: bool + 是否自动重置计分板 + author: str + 作者名称 + max_height: int + 生成结构最大高度 + + Returns + ------- + tuple[int指令数量, int音乐总延迟, tuple[int,]结构大小, tuple[int,]终点坐标] + """ + + cmdlist, command_count, max_score = midi_cvt.to_command_list_in_score( + scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier + ) + + if not os.path.exists(data_cfg.dist_path): + os.makedirs(data_cfg.dist_path) + + with open( + os.path.abspath( + os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx") + ), + "w+", + ) as f: + f.write("BD@") + + _bytes = ( + b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00" + ) + + cmdBytes, size, finalPos = commands_to_BDX_bytes( + midi_cvt.music_command_list + + ( + [ + SingleCommand( + command="scoreboard players reset @a[scores={" + + scoreboard_name + + "=" + + str(max_score + 20) + + "}] " + + scoreboard_name, + annotation="自动重置计分板", + ) + ] + if auto_reset + else [] + ), + max_height - 1, + ) + + if data_cfg.progressbar_style: + pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes( + midi_cvt.form_progress_bar( + max_score, scoreboard_name, data_cfg.progressbar_style + ), + max_height - 1, + ) + _bytes += pgbBytes + _bytes += bdx_move(y, -pgbNowPos[1]) + _bytes += bdx_move(z, -pgbNowPos[2]) + _bytes += bdx_move(x, 2) + + size[0] += 2 + pgbSize[0] + size[1] = max(size[1], pgbSize[1]) + size[2] = max(size[2], pgbSize[2]) + + _bytes += cmdBytes + + with open( + os.path.abspath( + os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx") + ), + "ab+", + ) as f: + f.write(brotli.compress(_bytes + b"XE")) + + return command_count, max_score, size, finalPos + + +def to_BDX_file_in_delay( + midi_cvt: MidiConvert, + data_cfg: ConvertConfig, + player: str = "@a", + author: str = "Eilles", + max_height: int = 64, +): + """ + 使用method指定的转换算法,将midi转换为BDX结构文件 + + Parameters + ---------- + midi_cvt: MidiConvert 对象 + 用于转换的MidiConvert对象 + data_cfg: ConvertConfig 对象 + 部分转换通用参数 + player: str + 玩家选择器,默认为`@a` + author: str + 作者名称 + max_height: int + 生成结构最大高度 + + Returns + ------- + tuple[int指令数量, int音乐总延迟, tuple[int,]结构大小, tuple[int,]终点坐标] + """ + + cmdlist, max_delay = midi_cvt.to_command_list_in_delay( + data_cfg.volume_ratio, + data_cfg.speed_multiplier, + player, + ) + + if not os.path.exists(data_cfg.dist_path): + os.makedirs(data_cfg.dist_path) + + with open( + os.path.abspath( + os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx") + ), + "w+", + ) as f: + f.write("BD@") + + _bytes = ( + b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00" + ) + + + cmdBytes, size, finalPos = commands_to_BDX_bytes(cmdlist, max_height - 1) + + if data_cfg.progressbar_style: + scb_name = midi_cvt.midi_music_name[:3] + "Pgb" + _bytes += form_command_block_in_BDX_bytes( + r"scoreboard objectives add {} dummy {}计".replace(r"{}", scb_name), + 1, + customName="初始化进度条", + ) + _bytes += bdx_move(z, 2) + _bytes += form_command_block_in_BDX_bytes( + r"scoreboard players add {} {} 1".format(player, scb_name), + 1, + 1, + customName="显示进度条并加分", + ) + _bytes += bdx_move(y, 1) + pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes( + midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style), + max_height - 1, + ) + _bytes += pgbBytes + _bytes += bdx_move(y, -1 - pgbNowPos[1]) + _bytes += bdx_move(z, -2 - pgbNowPos[2]) + _bytes += bdx_move(x, 2) + _bytes += form_command_block_in_BDX_bytes( + r"scoreboard players reset {} {}".format(player, scb_name), + 1, + customName="置零进度条", + ) + _bytes += bdx_move(y, 1) + size[0] += 2 + pgbSize[0] + size[1] = max(size[1], pgbSize[1]) + size[2] = max(size[2], pgbSize[2]) + + size[1] += 1 + _bytes += cmdBytes + + with open( + os.path.abspath( + os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx") + ), + "ab+", + ) as f: + f.write(brotli.compress(_bytes + b"XE")) + + return len(cmdlist), max_delay, size, finalPos diff --git a/Musicreater/plugin/common.py b/Musicreater/plugin/common.py index 94684c0..2aff4a6 100644 --- a/Musicreater/plugin/common.py +++ b/Musicreater/plugin/common.py @@ -1,6 +1,20 @@ -''' -存放通用的普遍性的内容 -''' +# -*- coding: utf-8 -*- +""" +存放通用的普遍性的插件内容 +""" + +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + import math @@ -11,4 +25,3 @@ def bottem_side_length_of_smallest_square_bottom_box(total: int, maxHeight: int) :param maxHeight: 最大高度 :return: 外切正方形的边长 int""" return math.ceil(math.sqrt(math.ceil(total / maxHeight))) - diff --git a/Musicreater/plugin/funcpack/__init__.py b/Musicreater/plugin/funcpack/__init__.py new file mode 100644 index 0000000..76e4e9c --- /dev/null +++ b/Musicreater/plugin/funcpack/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +""" +用以生成函数附加包的附加功能 + +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +__all__ = [] +__author__ = (("金羿", "Eilles Wan"),) + +from .main import * + diff --git a/Musicreater/plugin/funcpack/main.py b/Musicreater/plugin/funcpack/main.py new file mode 100644 index 0000000..6caebb9 --- /dev/null +++ b/Musicreater/plugin/funcpack/main.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +import json +import os +import shutil +from typing import Tuple + +from ...main import MidiConvert +from ..archive import behavior_mcpack_manifest, compress_zipfile +from ..main import ConvertConfig + + +def to_function_addon_in_score( + midi_cvt: MidiConvert, + data_cfg: ConvertConfig, + scoreboard_name: str = "mscplay", + auto_reset: bool = False, +) -> Tuple[int, int]: + """ + 将midi以计分播放器形式转换为我的世界函数附加包 + + Parameters + ---------- + midi_cvt: MidiConvert 对象 + 用于转换的MidiConvert对象 + data_cfg: ConvertConfig 对象 + 部分转换通用参数 + scoreboard_name: str + 我的世界的计分板名称 + auto_reset: bool + 是否自动重置计分板 + + Returns + ------- + tuple[int指令数量, int音乐总延迟] + """ + + cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_score( + scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier + ) + + # 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目,然后创建 + if os.path.exists(f"{data_cfg.dist_path}/temp/functions/"): + shutil.rmtree(f"{data_cfg.dist_path}/temp/functions/") + os.makedirs(f"{data_cfg.dist_path}/temp/functions/mscplay") + + # 写入manifest.json + open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8").write( + json.dumps( + behavior_mcpack_manifest("midiPlay"), + indent=4, + ) + ) + + # 将命令列表写入文件 + index_file = open( + f"{data_cfg.dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8" + ) + for i in range(len(cmdlist)): + index_file.write(f"function mscplay/track{i + 1}\n") + with open( + f"{data_cfg.dist_path}/temp/functions/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[scores={" + + scoreboard_name + + "=1..}] " + + scoreboard_name + + " 1\n", + ( + "scoreboard players reset @a[scores={" + + scoreboard_name + + "=" + + str(maxscore + 20) + + "..}]" + + f" {scoreboard_name}\n" + ) + if auto_reset + else "", + f"function mscplay/progressShow\n" if data_cfg.progressbar_style else "", + ) + ) + + if data_cfg.progressbar_style: + with open( + f"{data_cfg.dist_path}/temp/functions/mscplay/progressShow.mcfunction", + "w", + encoding="utf-8", + ) as f: + f.writelines( + "\n".join( + [ + single_cmd.cmd + for single_cmd in midi_cvt.form_progress_bar( + maxscore, scoreboard_name, data_cfg.progressbar_style + ) + ] + ) + ) + + index_file.close() + + if os.path.exists(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack"): + os.remove(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack") + compress_zipfile( + f"{data_cfg.dist_path}/temp/", + f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack", + ) + + shutil.rmtree(f"{data_cfg.dist_path}/temp/") + + return maxlen, maxscore diff --git a/Musicreater/plugin/main.py b/Musicreater/plugin/main.py new file mode 100644 index 0000000..3bca4c1 --- /dev/null +++ b/Musicreater/plugin/main.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +""" +存放附加内容功能 +""" + +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +from dataclasses import dataclass +from typing import Tuple, Union + +from ..constants import DEFAULT_PROGRESSBAR_STYLE + + +@dataclass(init=False) +class ConvertConfig: + """ + 转换通用设置存储类 + """ + + volume_ratio: float + """音量比例""" + + speed_multiplier: float + """速度倍率""" + + progressbar_style: Tuple[str, Tuple[str, str]] + """进度条样式组""" + + dist_path: str + """输出目录""" + + def __init__( + self, + output_path: str, + volume: float = 1.0, + speed: float = 1.0, + progressbar: Union[bool, Tuple[str, Tuple[str, str]]] = True, + ): + """ + 将已经转换好的数据内容指令载入MC可读格式 + + Parameters + ---------- + output_path: str + 生成内容的输出目录 + volume: float + 音量比率,范围为(0,1],其原理为在距离玩家 (1 / volume -1) 的地方播放音频 + speed: float + 速度倍率,注意:这里的速度指的是播放速度倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed + progressbar: bool|tuple[str, Tuple[str,]] + 进度条,当此参数为 `True` 时使用默认进度条,为其他的**值为真**的参数时识别为进度条自定义参数,为其他**值为假**的时候不生成进度条 + + """ + + self.dist_path = output_path + """输出目录""" + + self.volume_ratio = volume + """音量比例""" + + self.speed_multiplier = speed + """速度倍率""" + + if progressbar: + # 此处是对于仅有 True 的参数和自定义参数的判断 + # 改这一段没🐎 + if progressbar is True: + self.progressbar_style = DEFAULT_PROGRESSBAR_STYLE + """进度条样式组""" + else: + self.progressbar_style = progressbar + """进度条样式组""" + else: + self.progressbar_style = None + """进度条样式组""" diff --git a/Musicreater/plugin/mcstructfile/__init__.py b/Musicreater/plugin/mcstructfile/__init__.py new file mode 100644 index 0000000..72480e1 --- /dev/null +++ b/Musicreater/plugin/mcstructfile/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +""" +用以生成单个mcstructure文件的附加功能 + +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +__all__ = [] +__author__ = (("金羿", "Eilles Wan"),) + +from .main import * + diff --git a/Musicreater/plugin/mcstructfile/main.py b/Musicreater/plugin/mcstructfile/main.py new file mode 100644 index 0000000..0999392 --- /dev/null +++ b/Musicreater/plugin/mcstructfile/main.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + +import os + +from ...exceptions import CommandFormatError +from ...main import MidiConvert +from ..main import ConvertConfig +from ..mcstructure import commands_to_structure + + +def to_mcstructure_file_in_delay( + midi_cvt: MidiConvert, + data_cfg: ConvertConfig, + player: str = "@a", + max_height: int = 64, +): + """ + 将midi以延迟播放器形式转换为mcstructure结构文件 + + Parameters + ---------- + midi_cvt: MidiConvert 对象 + 用于转换的MidiConvert对象 + data_cfg: ConvertConfig 对象 + 部分转换通用参数 + player: str + 玩家选择器,默认为`@a` + max_height: int + 生成结构最大高度 + + Returns + ------- + tuple[tuple[int,]结构大小, int音乐总延迟] + """ + + if midi_cvt.enable_old_exe_format: + raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。") + + cmd_list, max_delay = midi_cvt.to_command_list_in_delay( + data_cfg.volume_ratio, + data_cfg.speed_multiplier, + player, + ) + + if not os.path.exists(data_cfg.dist_path): + os.makedirs(data_cfg.dist_path) + + struct, size, end_pos = commands_to_structure(cmd_list, max_height - 1) + + with open( + os.path.abspath( + os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.mcstructure") + ), + "wb+", + ) as f: + struct.dump(f) + + return size, max_delay diff --git a/Musicreater/plugin/mcstructpack/__init__.py b/Musicreater/plugin/mcstructpack/__init__.py new file mode 100644 index 0000000..1073a36 --- /dev/null +++ b/Musicreater/plugin/mcstructpack/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +""" +用以生成结构附加包的附加功能 + +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +__all__ = [] +__author__ = (("金羿", "Eilles Wan"),) + +from .main import * + diff --git a/Musicreater/plugin/mcstructpack/main.py b/Musicreater/plugin/mcstructpack/main.py new file mode 100644 index 0000000..959c1e4 --- /dev/null +++ b/Musicreater/plugin/mcstructpack/main.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + +import json +import os +import shutil + +from TrimMCStruct import Structure + +from ...exceptions import CommandFormatError +from ...main import MidiConvert +from ..archive import behavior_mcpack_manifest, compress_zipfile +from ..main import ConvertConfig +from ..mcstructure import (commands_to_structure, + form_command_block_in_NBT_struct) + + +def to_mcstructure_addon_in_delay( + midi_cvt: MidiConvert, + data_cfg: ConvertConfig, + player: str = "@a", + max_height: int = 64, +): + """ + 将midi以延迟播放器形式转换为mcstructure结构文件后打包成附加包,并在附加包中生成相应地导入函数 + + Parameters + ---------- + midi_cvt: MidiConvert 对象 + 用于转换的MidiConvert对象 + data_cfg: ConvertConfig 对象 + 部分转换通用参数 + player: str + 玩家选择器,默认为`@a` + max_height: int + 生成结构最大高度 + + Returns + ------- + tuple[int指令数量, int音乐总延迟] + """ + + if midi_cvt.enable_old_exe_format: + raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。") + + command_list, max_delay = midi_cvt.to_command_list_in_delay( + data_cfg.volume_ratio, + data_cfg.speed_multiplier, + player, + ) + + if not os.path.exists(data_cfg.dist_path): + os.makedirs(data_cfg.dist_path) + + # 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建 + if os.path.exists(f"{data_cfg.dist_path}/temp/"): + shutil.rmtree(f"{data_cfg.dist_path}/temp/") + os.makedirs(f"{data_cfg.dist_path}/temp/functions/") + os.makedirs(f"{data_cfg.dist_path}/temp/structures/") + + # 写入manifest.json + with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f: + json.dump( + behavior_mcpack_manifest( + pack_description=f"Music {midi_cvt.midi_music_name} Play pack, structure in delay - Generated by Musicreater", + pack_name=midi_cvt.midi_music_name, + modules_description=f"None - Generated by Musicreater", + ), + fp=f, + ) + + # 将命令列表写入文件 + index_file = open( + f"{data_cfg.dist_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( + data_cfg.dist_path, + "temp/structures/", + f"{midi_cvt.midi_music_name}_main.mcstructure", + ) + ), + "wb+", + ) as f: + struct.dump(f) + + del struct + + if data_cfg.progressbar_style: + scb_name = midi_cvt.midi_music_name[:3] + "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, + alwaysRun=False, + customName="显示进度条并加分", + ), + ) + + with open( + os.path.abspath( + os.path.join( + data_cfg.dist_path, + "temp/structures/", + f"{midi_cvt.midi_music_name}_start.mcstructure", + ) + ), + "wb+", + ) as f: + struct_a.dump(f) + + index_file.write(f"structure load {midi_cvt.midi_music_name}_start ~ ~ ~1\n") + + pgb_struct, pgbSize, pgbNowPos = commands_to_structure( + midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style), + max_height - 1, + ) + + with open( + os.path.abspath( + os.path.join( + data_cfg.dist_path, + "temp/structures/", + f"{midi_cvt.midi_music_name}_pgb.mcstructure", + ) + ), + "wb+", + ) as f: + pgb_struct.dump(f) + + index_file.write(f"structure load {midi_cvt.midi_music_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, + 0, + alwaysRun=False, + customName="重置进度条计分板", + ), + ) + + with open( + os.path.abspath( + os.path.join( + data_cfg.dist_path, + "temp/structures/", + f"{midi_cvt.midi_music_name}_reset.mcstructure", + ) + ), + "wb+", + ) as f: + struct_a.dump(f) + + del struct_a, pgb_struct + + index_file.write( + f"structure load {midi_cvt.midi_music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n" + ) + + index_file.write( + f"structure load {midi_cvt.midi_music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n" + ) + + else: + index_file.write(f"structure load {midi_cvt.midi_music_name}_main ~ ~ ~1\n") + + index_file.close() + + if os.path.exists(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack"): + os.remove(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack") + compress_zipfile( + f"{data_cfg.dist_path}/temp/", + f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack", + ) + + shutil.rmtree(f"{data_cfg.dist_path}/temp/") + + return len(command_list), max_delay diff --git a/Musicreater/plugin/mcstructure.py b/Musicreater/plugin/mcstructure.py index 33e13cb..d7f4be1 100644 --- a/Musicreater/plugin/mcstructure.py +++ b/Musicreater/plugin/mcstructure.py @@ -1,12 +1,31 @@ -''' +# -*- coding: utf-8 -*- +""" 存放有关MCSTRUCTURE结构操作的内容 -''' +""" +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +from typing import List + +from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long + +from ..subclass import SingleCommand from .common import bottem_side_length_of_smallest_square_bottom_box -from TrimMCStruct import Structure, Block, TAG_Long, TAG_Byte + def form_note_block_in_NBT_struct( - note: int, coordinate: tuple, instrument: str = "note.harp", powered: bool = False + note: int, coordinate: tuple, instrument: str = "note.harp", powered: bool = False ): """生成音符盒方块 :param note: `int`(0~24) @@ -40,15 +59,12 @@ def form_note_block_in_NBT_struct( ) -def form_repeater_in_NBT_struct( - delay: int, facing: int -): +def form_repeater_in_NBT_struct(delay: int, facing: int): """生成中继器方块 :param facing: :param delay: 1~4 :return Block()""" - return Block( "minecraft", "unpowered_repeater", @@ -60,16 +76,16 @@ def form_repeater_in_NBT_struct( def form_command_block_in_NBT_struct( - command: str, - coordinate: tuple, - particularValue: int, - impluse: int = 0, - condition: bool = False, - alwaysRun: bool = True, - tickDelay: int = 0, - customName: str = "", - executeOnFirstTick: bool = False, - trackOutput: bool = True, + command: str, + coordinate: tuple, + particularValue: int, + impluse: int = 0, + condition: bool = False, + alwaysRun: bool = True, + tickDelay: int = 0, + customName: str = "", + executeOnFirstTick: bool = False, + trackOutput: bool = True, ): """ 使用指定项目返回指定的指令方块结构 @@ -116,7 +132,6 @@ def form_command_block_in_NBT_struct( :return:str """ - return Block( "minecraft", "command_block" @@ -154,16 +169,15 @@ def form_command_block_in_NBT_struct( def commands_to_structure( - commands: list, - max_height: int = 64, + commands: List[SingleCommand], + max_height: int = 64, ): """ - :param commands: 指令列表(指令, 延迟) + :param commands: 指令列表 :param max_height: 生成结构最大高度 - :return 成功与否,成功返回(结构类,结构占用大小),失败返回(False,str失败原因) + :return 结构类,结构占用大小,终点坐标 """ - _sideLength = bottem_side_length_of_smallest_square_bottom_box( len(commands), max_height ) @@ -179,31 +193,31 @@ def commands_to_structure( now_z = 0 now_x = 0 - for cmd, delay in commands: + for command in commands: coordinate = (now_x, now_y, now_z) struct.set_block( coordinate, form_command_block_in_NBT_struct( - command=cmd, + command=command.command_text, 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))) + ((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)) + ((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="", + tickDelay=command.delay, + customName=command.annotation_text, executeOnFirstTick=False, trackOutput=True, ), @@ -219,7 +233,7 @@ def commands_to_structure( now_z += 1 if z_forward else -1 if ((now_z >= _sideLength) and z_forward) or ( - (now_z < 0) and (not z_forward) + (now_z < 0) and (not z_forward) ): now_z -= 1 if z_forward else -1 z_forward = not z_forward @@ -234,4 +248,3 @@ def commands_to_structure( ), (now_x, now_y, now_z), ) - diff --git a/Musicreater/previous.py b/Musicreater/previous/convert.py similarity index 96% rename from Musicreater/previous.py rename to Musicreater/previous/convert.py index 40e4c9c..d26f6b8 100644 --- a/Musicreater/previous.py +++ b/Musicreater/previous/convert.py @@ -1,13 +1,27 @@ -''' -旧版本功能以及已经弃用的函数 -''' +# -*- coding: utf-8 -*- +""" +旧版本转换功能以及已经弃用的函数 +""" -from .exceptions import * -from .main import midiConvert +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +from ..exceptions import * +from ..main import MidiConvert def to_command_list_method1( - self: midiConvert, + self: MidiConvert, scoreboard_name: str = "mscplay", MaxVolume: float = 1.0, speed: float = 1.0, @@ -77,7 +91,7 @@ def to_command_list_method1( # 原本这个算法的转换效果应该和上面的算法相似的 def _toCmdList_m2( - self: midiConvert, + self: MidiConvert, scoreboard_name: str = "mscplay", MaxVolume: float = 1.0, speed: float = 1.0, @@ -214,7 +228,7 @@ def _toCmdList_m2( def _toCmdList_withDelay_m1( - self: midiConvert, + self: MidiConvert, MaxVolume: float = 1.0, speed: float = 1.0, player: str = "@a", @@ -288,7 +302,7 @@ def _toCmdList_withDelay_m1( def _toCmdList_withDelay_m2( - self: midiConvert, + self: MidiConvert, MaxVolume: float = 1.0, speed: float = 1.0, player: str = "@a", diff --git a/Musicreater/subclass.py b/Musicreater/subclass.py index 6e29e63..29f3874 100644 --- a/Musicreater/subclass.py +++ b/Musicreater/subclass.py @@ -1,6 +1,20 @@ -''' +# -*- coding: utf-8 -*- + +""" 存储许多非主要的相关类 -''' +""" + +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md from dataclasses import dataclass @@ -11,14 +25,20 @@ T = TypeVar("T") # Declare type variable @dataclass(init=False) class SingleNote: + """存储单个音符的类""" + instrument: int """乐器编号""" + note: int """音符编号""" + velocity: int """力度/响度""" + startTime: int """开始之时 ms""" + lastTime: int """音符持续时间 ms""" @@ -76,6 +96,80 @@ class SingleNote: } +@dataclass(init=False) +class SingleCommand: + """存储单个指令的类""" + + command_text: str + """指令文本""" + + conditional: bool + """执行是否有条件""" + + delay: int + """执行的延迟""" + + annotation_text: str + """指令注释""" + + def __init__( + self, + command: str, + condition: bool = False, + tick_delay: int = 0, + annotation: str = "", + ): + """ + 存储单个指令的类 + + Parameters + ---------- + command: str + 指令 + condition: bool + 是否有条件 + tick_delay: int + 执行延时 + annotation: str + 注释 + """ + self.command_text = command + self.conditional = condition + self.delay = tick_delay + self.annotation_text = annotation + + def copy(self): + return SingleCommand( + command=self.command_text, + condition=self.conditional, + tick_delay=self.delay, + annotation=self.annotation_text, + ) + + @property + def cmd(self) -> str: + """ + 我的世界函数字符串(包含注释) + """ + return self.__str__() + + def __str__(self) -> str: + """ + 转为我的世界函数文件格式(包含注释) + """ + return "#[{cdt}]<{delay}> {ant}\n{cmd}".format( + cdt="CDT" if self.conditional else "", + delay=self.delay, + ant=self.annotation_text, + cmd=self.command_text, + ) + + def __eq__(self, other) -> bool: + if not isinstance(other, self.__class__): + return False + return self.__str__() == other.__str__() + + class MethodList(list): """函数列表,列表中的所有元素均为函数""" diff --git a/Musicreater/utils.py b/Musicreater/utils.py index d5b0971..eb31735 100644 --- a/Musicreater/utils.py +++ b/Musicreater/utils.py @@ -1,16 +1,30 @@ -''' +# -*- coding: utf-8 -*- +""" 存放主程序所必须的功能性内容 -''' +""" +""" +版权所有 © 2023 音·创 开发者 +Copyright © 2023 all the developers of Musicreater -def mctick2timestr(mc_tick: int): +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿穆组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + +from typing import Dict + +def mctick2timestr(mc_tick: int) -> str: """ 将《我的世界》的游戏刻计转为表示时间的字符串 """ return str(int(int(mc_tick / 20) / 60)) + ":" + str(int(int(mc_tick / 20) % 60)) -def empty_midi_channels(channel_count: int = 17) -> dict: +def empty_midi_channels(channel_count: int = 17) -> Dict[int, Dict]: """ 空MIDI通道字典 """ diff --git a/docs/库的生成与功能文档.md b/docs/库的生成与功能文档.md index 2180945..2411320 100644 --- a/docs/库的生成与功能文档.md +++ b/docs/库的生成与功能文档.md @@ -10,19 +10,104 @@ # 库的简单调用 -参见[example.py的相关部分](../example.py#L120),使用此库进行MIDI转换非常简单。 +参见[example.py的相关部分](../example.py),使用此库进行MIDI转换非常简单。 + +- 在导入转换库后,使用 MidiConvert 类建立转换对象(读取Midi文件) + + 音·创库支持新旧两种execute语法,需要在对象实例化时指定 + ```python + # 导入音·创库 + import Musicreater + + # 指定是否使用旧的execute指令语法(即1.18及以前的《我的世界:基岩版》语法) + old_execute_format = False + + # 可以通过文件地址自动读取 + cvt_mid = Musicreater.MidiConvert.from_midi_file( + "Midi文件地址", + old_exe_format=old_execute_format + ) + + # 也可以导入Mido对象 + cvt_mid = Musicreater.MidiConvert( + mido.MidiFile("Midi文件地址"), + "音乐名称", + old_exe_format=old_execute_format + ) + ``` + +- 获取 Midi 音乐经转换后的播放指令 + + ```python + # 通过函数 to_command_list_in_score, to_command_list_in_delay + # 分别可以得到 + # 以计分板作为播放器的指令对象列表、以延迟作为播放器的指令对象列表 + # 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档 + + # 使用 to_command_list_in_score 函数进行转换之后,返回值有三个 + # 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表 + # 也就是列表套列表 + # 但是,在对象内部所存储的数据却不会如此嵌套 + command_channel_list, command_count, max_score = cvt_mid.to_command_list_in_score( + "计分板名称", + 1.0, # 音量比率 + 1.0, # 速度倍率 + ) + + # 使用 to_command_list_in_delay 转换后的返回值只有两个 + # 但是第一个返回值没有列表套列表 + command_list, max_delay = cvt_mid.to_command_list_in_delay( + 1.0, # 音量比率 + 1.0, # 速度倍率 + "@a", # 玩家选择器 + ) + + # 运行之后,指令和总延迟会存储至对象内 + print( + "音乐长度:{}/游戏刻".format( + cvt_mid.music_tick_num + ) + ) + print( + "指令如下:\n{}".format( + cvt_mid.music_command_list + ) + ) + ``` + +- 除了获取播放指令外,还可以获取进度条指令 + + ```python + # 通过函数 form_progress_bar 可以获得 + # 以计分板为载体所生成的进度条的指令对象列表 + # 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档 + + # 使用 form_progress_bar 函数进行转换之后,返回值有三个 + # 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表 + # 也就是列表套列表 + cvt_mid.form_progress_bar( + max_score, # 音乐时长游戏刻 + scoreboard_name, # 进度条使用的计分板名称 + progressbar_style, # 进度条样式组(详见下方) + ) + + # 同上面生成播放指令的理,进度条指令也混存储至对象内 + print( + "进度条指令如下:\n{}".format( + cvt_mid.progress_bar_command + ) + ) + ``` + + 在上面的代码中,进度条样式是可以自定义的,详见[下方说明](%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md#进度条自定义)。 + +- 转换成指令是一个方面,接下来是再转换为可以导入MC的格式 + +***诶哟我好累不想写了,等有时间再写,你们就看样例程序的用法吧,无量天尊*** + +***这后面一直到下一个小节的内容全是没改的旧内容,肯定有问题*** ```python -import Musicreater # 导入转换库 - -old_execute_format = False # 指定是否使用旧的execute指令语法(即1.18及以前的《我的世界:基岩版》语法) - -# 首先新建转换对象。 -conversion = Musicreater.midiConvert(enable_old_exe_format = old_execute_format) -# 值得注意的是,一个转换对象可以转换多个文件。 -# 也就是在实例化的时候不进行对文件的绑定。 -# 如果有调试需要,可以在实例化时传入参数 debug = True -# 如:conversion = Musicreater.midiConvert(debug=True) # 设置输入输出地址,并指定execute指令语法 # 地址都为字符串类型,不能传入文件流 @@ -51,7 +136,7 @@ convertion_result = conversion.to_BDX_file(method_id,*prompts) convertion_result = conversion.to_BDX_file_with_delay(method_id,*prompts) # 转换结果是一个元组。 -# 若其转换成功,则前三位必为 +# 若其转换成功,则前二位必为 # True, 指令数量, 最大延迟 # 其中,最大延迟可以理解为计分板的最大值 # 如果转换失败,暂时还没有定返回值的规则 @@ -101,11 +186,11 @@ print(convertion_result) 所以,结构的生成形状依照给定的高度和内含指令的数量决定。其 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整。用数学公式的方式表达,则是: - $$MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor$$ + $$ MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor $$ - 其中,$MaxZ$即生成结构的$Z$轴最大延伸长度,$NoC$表示链结构中所含指令方块的个数,$MaxH$表示给定的生成结构的最大高度。 + 其中,$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`。如此往复,直至指令链堆叠完成。 ## 播放器 @@ -200,5 +285,5 @@ print(convertion_result) `(r'▶ %%N [ %%s/%^s %%% __________ %%t|%^t]',('§e=§r', '§7=§r'))` -*对了!为了避免生成错误,请尽量避免使用标识符作为定义样式字符串的其他部分* +*为了避免生成错误,请尽量避免使用标识符作为定义样式字符串的其他部分* diff --git a/docs/生成文件的使用说明.md b/docs/生成文件的使用说明.md index fbd6950..2d814c4 100644 --- a/docs/生成文件的使用说明.md +++ b/docs/生成文件的使用说明.md @@ -16,14 +16,24 @@ 支持的文件后缀:`.MCPACK` -1. 导入附加包 -2. 在一个循环方块中输入指令 `function index` -3. 将需要聆听音乐的实体的播放所用计分板设置为 `1` -4. 激活循环方块 -5. 若想要暂停播放,可以停止循环指令方块的激活状态 -6. 若想要重置某实体的播放,可以将其播放用的计分板重置 +- 计分板播放器 -> 其中 步骤三 和 步骤四 的顺序可以调换。 + 1. 导入附加包 + 2. 在一个循环方块中输入指令 `function index` + 3. 将需要聆听音乐的实体的播放所用计分板设置为 `1` + 4. 激活循环方块 + 5. 若想要暂停播放,可以停止循环指令方块的激活状态 + 6. 若想要重置某实体的播放,可以将其播放用的计分板重置 + + > 其中 步骤三 和 步骤四 的顺序可以调换。 + +- 延迟播放器 + + 1. 导入附加包 + 2. 在聊天框输入指令 `function index` + 3. 同时激活所生成的循环和脉冲指令方块 + + > 需要注意的是,循环指令方块需要一直激活直到音乐结束 ## 结构格式 @@ -41,7 +51,7 @@ - 计分板播放器 2. 在所生成的第一个指令方块前,放置一个循环指令方块,其朝向应当对着所生成的第一个方块 - 3. 在循环指令方块中输入使播放对象的播放用计分板加分的指令,延迟为`0`,每次循环增加`1`分 + 3. 在循环指令方块中输入使播放对象的播放用计分板加分的指令,延迟为 `0`,每次循环增加 `1` 分 4. 激活循环方块 5. 若想要暂停播放,可以停止循环指令方块的激活状态 6. 若想要重置某实体的播放,可以将其播放用的计分板重置 diff --git a/docs/转换乐器对照表.md b/docs/转换乐器对照表.md index 8887e91..19855c6 100644 --- a/docs/转换乐器对照表.md +++ b/docs/转换乐器对照表.md @@ -1,2 +1,198 @@ +**_注意!本文档中的对照表,版权归属于音·创作者,并按照本仓库根目录下 LICENSE.md 中规定开源_** -# 暂无 +**_使用时请遵循规定_** + +- 版权所有 © 2023 音·创 开发者 +- Copyright © 2023 all the developers of Musicreater + +* 开源相关声明请见 仓库根目录下的 License.md +* Terms & Conditions: License.md in the root directory + +音·创 开发交流群 861684859\ +Email TriM-Organization@hotmail.com\ +若需转载或借鉴 许可声明请查看仓库根目录下的 License.md + +# 乐音乐器 + +| Midi 乐器值 | 我的世界音符名称 | 我的世界音调偏移参数 | +| ----------- | ------------------- | -------------------- | +| 0 | note.harp | 6 | +| 1 | note.harp | 6 | +| 2 | note.pling | 6 | +| 3 | note.harp | 6 | +| 4 | note.pling | 6 | +| 5 | note.pling | 6 | +| 6 | note.harp | 6 | +| 7 | note.harp | 6 | +| 8 | note.share | 7 | +| 9 | note.harp | 6 | +| 10 | note.didgeridoo | 8 | +| 11 | note.harp | 6 | +| 12 | note.xylophone | 4 | +| 13 | note.chime | 4 | +| 14 | note.harp | 6 | +| 15 | note.harp | 6 | +| 16 | note.bass | 8 | +| 17 | note.harp | 6 | +| 18 | note.harp | 6 | +| 19 | note.harp | 6 | +| 20 | note.harp | 6 | +| 21 | note.harp | 6 | +| 22 | note.harp | 6 | +| 23 | note.guitar | 7 | +| 24 | note.guitar | 7 | +| 25 | note.guitar | 7 | +| 26 | note.guitar | 7 | +| 27 | note.guitar | 7 | +| 28 | note.guitar | 7 | +| 29 | note.guitar | 7 | +| 30 | note.guitar | 7 | +| 31 | note.bass | 8 | +| 32 | note.bass | 8 | +| 33 | note.bass | 8 | +| 34 | note.bass | 8 | +| 35 | note.bass | 8 | +| 36 | note.bass | 8 | +| 37 | note.bass | 8 | +| 38 | note.bass | 8 | +| 39 | note.bass | 8 | +| 40 | note.harp | 6 | +| 41 | note.harp | 6 | +| 42 | note.harp | 6 | +| 43 | note.harp | 6 | +| 44 | note.iron_xylophone | 6 | +| 45 | note.guitar | 7 | +| 46 | note.harp | 6 | +| 47 | note.harp | 6 | +| 48 | note.guitar | 7 | +| 49 | note.guitar | 7 | +| 50 | note.bit | 6 | +| 51 | note.bit | 6 | +| 52 | note.harp | 6 | +| 53 | note.harp | 6 | +| 54 | note.bit | 6 | +| 55 | note.flute | 5 | +| 56 | note.flute | 5 | +| 57 | note.flute | 5 | +| 58 | note.flute | 5 | +| 59 | note.flute | 5 | +| 60 | note.flute | 5 | +| 61 | note.flute | 5 | +| 62 | note.flute | 5 | +| 63 | note.flute | 5 | +| 64 | note.bit | 6 | +| 65 | note.bit | 6 | +| 66 | note.bit | 6 | +| 67 | note.bit | 6 | +| 68 | note.flute | 5 | +| 69 | note.harp | 6 | +| 70 | note.harp | 6 | +| 71 | note.flute | 5 | +| 72 | note.flute | 5 | +| 73 | note.flute | 5 | +| 74 | note.harp | 6 | +| 75 | note.flute | 5 | +| 76 | note.harp | 6 | +| 77 | note.harp | 6 | +| 78 | note.harp | 6 | +| 79 | note.harp | 6 | +| 80 | note.bit | 6 | +| 81 | note.bit | 6 | +| 82 | note.bit | 6 | +| 83 | note.bit | 6 | +| 84 | note.bit | 6 | +| 85 | note.bit | 6 | +| 86 | note.bit | 6 | +| 87 | note.bit | 6 | +| 88 | note.bit | 6 | +| 89 | note.bit | 6 | +| 90 | note.bit | 6 | +| 91 | note.bit | 6 | +| 92 | note.bit | 6 | +| 93 | note.bit | 6 | +| 94 | note.bit | 6 | +| 95 | note.bit | 6 | +| 96 | note.bit | 6 | +| 97 | note.bit | 6 | +| 98 | note.bit | 6 | +| 99 | note.bit | 6 | +| 100 | note.bit | 6 | +| 101 | note.bit | 6 | +| 102 | note.bit | 6 | +| 103 | note.bit | 6 | +| 104 | note.harp | 6 | +| 105 | note.banjo | 6 | +| 106 | note.harp | 6 | +| 107 | note.harp | 6 | +| 108 | note.harp | 6 | +| 109 | note.harp | 6 | +| 110 | note.harp | 6 | +| 111 | note.guitar | 7 | +| 112 | note.harp | 6 | +| 113 | note.bell | 4 | +| 114 | note.harp | 6 | +| 115 | note.cow_bell | 5 | +| 116 | note.bd | 7 | +| 117 | note.bass | 8 | +| 118 | note.bit | 6 | +| 119 | note.bd | 7 | +| 120 | note.guitar | 7 | +| 121 | note.harp | 6 | +| 122 | note.harp | 6 | +| 123 | note.harp | 6 | +| 124 | note.harp | 6 | +| 125 | note.hat | 7 | +| 126 | note.bd | 7 | +| 127 | note.snare | 7 | + +# 打击乐器 + +| Midi 打击乐器值 | 我的世界音符名称 | 我的世界音调偏移参数 | +| --------------- | ------------------- | -------------------- | +| 34 | note.bd | 7 | +| 35 | note.bd | 7 | +| 36 | note.hat | 7 | +| 37 | note.snare | 7 | +| 38 | note.snare | 7 | +| 39 | note.snare | 7 | +| 40 | note.hat | 7 | +| 41 | note.snare | 7 | +| 42 | note.hat | 7 | +| 43 | note.snare | 7 | +| 44 | note.snare | 7 | +| 45 | note.bell | 4 | +| 46 | note.snare | 7 | +| 47 | note.snare | 7 | +| 48 | note.bell | 4 | +| 49 | note.hat | 7 | +| 50 | note.bell | 4 | +| 51 | note.bell | 4 | +| 52 | note.bell | 4 | +| 53 | note.bell | 4 | +| 54 | note.bell | 4 | +| 55 | note.bell | 4 | +| 56 | note.snare | 7 | +| 57 | note.hat | 7 | +| 58 | note.chime | 4 | +| 59 | note.iron_xylophone | 6 | +| 60 | note.bd | 7 | +| 61 | note.bd | 7 | +| 62 | note.xylophone | 4 | +| 63 | note.xylophone | 4 | +| 64 | note.xylophone | 4 | +| 65 | note.hat | 7 | +| 66 | note.bell | 4 | +| 67 | note.bell | 4 | +| 68 | note.hat | 7 | +| 69 | note.hat | 7 | +| 70 | note.flute | 5 | +| 71 | note.flute | 5 | +| 72 | note.hat | 7 | +| 73 | note.hat | 7 | +| 74 | note.xylophone | 4 | +| 75 | note.hat | 7 | +| 76 | note.hat | 7 | +| 77 | note.xylophone | 4 | +| 78 | note.xylophone | 4 | +| 79 | note.bell | 4 | +| 80 | note.bell | 4 | diff --git a/example.py b/example.py index 00f0fe4..cfd02a3 100644 --- a/example.py +++ b/example.py @@ -17,8 +17,12 @@ Terms & Conditions: ./License.md """ import os -import Musicreater +import Musicreater +from Musicreater.plugin import ConvertConfig +from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score +from Musicreater.plugin.funcpack import to_function_addon_in_score +from Musicreater.plugin.mcstructpack import to_mcstructure_addon_in_delay # 获取midi列表 midi_path = input(f"请输入MIDI路径:") @@ -27,17 +31,6 @@ midi_path = input(f"请输入MIDI路径:") # 获取输出地址 out_path = input(f"请输入输出路径:") -conversion = Musicreater.midiConvert() - - -def isMethodOK(sth: str): - if int(sth) in range(1, len(conversion.methods) + 1): - return int(sth) - else: - raise ValueError - - -convert_method = int(input(f"请输入转换算法[1~{len(conversion.methods)}]:")) # 选择输出格式 fileFormat = int(input(f"请输入输出格式[BDX(1) 或 MCPACK(0)]:").lower()) @@ -57,14 +50,10 @@ def bool_str(sth: str) -> bool: raise ValueError("布尔字符串啊?") -debug = False - if os.path.exists("./demo_config.json"): import json prompts = json.load(open("./demo_config.json", "r", encoding="utf-8")) - if prompts[-1] == "debug": - debug = True prompts = prompts[:-1] else: prompts = [] @@ -103,45 +92,39 @@ else: ) if fileFormat == 1 else (), - ( + () + if playerFormat == 1 + else ( f"最大结构高度:", int, - ) - if fileFormat == 1 - else (), + ), ]: if args: prompts.append(args[1](input(args[0]))) -conversion = Musicreater.midiConvert(debug=debug, enable_old_exe_format=False) - print(f"正在处理 {midi_path} :") -conversion.convert(midi_path, out_path) -if debug: - with open("./records.json", "a", encoding="utf-8") as f: - json.dump(conversion.toDICT(), f) - f.write(5 * "\n") -conversion_result = ( - ( - conversion.to_mcpack(convert_method, *prompts) - if playerFormat == 1 - else conversion.to_mcpack_with_delay(convert_method, *prompts) +cvt_mid = Musicreater.MidiConvert.from_midi_file(midi_path, old_exe_format=False) +cvt_cfg = ConvertConfig(out_path, *prompts[:3]) + +print( + " 指令总长:{},最高延迟:{}".format( + *( + to_function_addon_in_score(cvt_mid, cvt_cfg, *prompts[3:]) + if playerFormat == 1 + else to_mcstructure_addon_in_delay(cvt_mid, cvt_cfg, *prompts[3:]) + ) ) - if fileFormat == 0 - else ( - conversion.to_BDX_file(convert_method, *prompts) - if playerFormat == 1 - else conversion.to_BDX_file_with_delay(convert_method, *prompts) +) if fileFormat == 0 else print( + " 指令总长:{},最高延迟:{},结构大小{},终点坐标{}".format( + *( + to_BDX_file_in_score(cvt_mid, cvt_cfg, *prompts[3:]) + if playerFormat == 1 + else to_BDX_file_in_delay(cvt_mid, cvt_cfg, *prompts[3:]) + ) ) ) -if conversion_result[0]: - print( - f" 指令总长:{conversion_result[1]},最高延迟:{conversion_result[2]}{f''',结构大小{conversion_result[3]},最末坐标{conversion_result[4]}''' if fileFormat == 1 else ''}" - ) -else: - print(f"失败:{conversion_result}") exitSth = input("回车退出").lower() if exitSth == "record": diff --git a/example_mcstructure.py b/example_mcstructure.py index b6407e6..e03edc5 100644 --- a/example_mcstructure.py +++ b/example_mcstructure.py @@ -1,8 +1,12 @@ -from Musicreater import midiConvert +import Musicreater +import Musicreater.plugin +import Musicreater.plugin.mcstructfile -conversion = midiConvert(enable_old_exe_format=False) -conversion.convert(input("midi路径:"), input("输出路径:")) - -conversion.to_mcstructure_file_with_delay( - 3, +print( + Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay( + Musicreater.MidiConvert.from_midi_file(input("midi路径:"), old_exe_format=False), + Musicreater.plugin.ConvertConfig( + input("输出路径:"), + ), + ) )