From f73c1be9448b84cf5ffd5fc0fa5948fb0e0a626b Mon Sep 17 00:00:00 2001 From: EillesWan Date: Sat, 1 Jul 2023 19:11:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E9=AB=98=E6=95=88=E7=9A=84=E7=AE=97?= =?UTF-8?q?=E6=B3=95=E7=AE=A1=E7=90=86=E4=B8=8E=E5=85=BC=E5=AE=B9=E6=80=A7?= =?UTF-8?q?=E5=92=8C=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=8C=E8=AF=A6=E8=A7=81=E6=A0=B7=E4=BE=8B=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=9B=E5=90=8C=E6=97=B6=E6=96=B0=E5=A2=9E=E5=AE=9E=E9=AA=8C?= =?UTF-8?q?=E7=AE=97=E6=B3=95=EF=BC=8C=E5=9C=A8=E5=85=B6=E4=B8=AD=E5=B0=9D?= =?UTF-8?q?=E8=AF=95=E4=B8=8B=E6=AC=A1=E6=9B=B4=E6=96=B0=E7=9A=84=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Musicreater/__init__.py | 12 +- Musicreater/exceptions.py | 12 +- Musicreater/experiment.py | 587 +++++++++++++++----- Musicreater/main.py | 233 +++----- Musicreater/plugin/__init__.py | 4 +- Musicreater/plugin/archive.py | 16 +- Musicreater/plugin/bdxfile/__init__.py | 5 +- Musicreater/plugin/bdxfile/main.py | 9 +- Musicreater/plugin/funcpack/__init__.py | 4 +- Musicreater/plugin/funcpack/main.py | 3 +- Musicreater/plugin/main.py | 4 +- Musicreater/plugin/mcstructfile/__init__.py | 4 +- Musicreater/plugin/mcstructfile/main.py | 5 +- Musicreater/plugin/mcstructpack/__init__.py | 4 +- Musicreater/plugin/mcstructpack/main.py | 5 +- Musicreater/plugin/mcstructure.py | 11 +- Musicreater/previous.py | 326 +++++++++++ Musicreater/previous/convert.py | 463 --------------- Musicreater/subclass.py | 41 +- Musicreater/utils.py | 14 +- docs/库的生成与功能文档.md | 4 +- docs/生成文件的使用说明.md | 2 +- example_futureFunction.py | 13 + setup.py | 2 +- 25 files changed, 964 insertions(+), 820 deletions(-) create mode 100644 Musicreater/previous.py delete mode 100644 Musicreater/previous/convert.py create mode 100644 example_futureFunction.py diff --git a/.gitignore b/.gitignore index 57a0f42..f6eff6c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ /llc_cli.py /utils test.py +RES.txt # Byte-compiled / optimized __pycache__/ diff --git a/Musicreater/__init__.py b/Musicreater/__init__.py index 51a2df6..3d27e5f 100644 --- a/Musicreater/__init__.py +++ b/Musicreater/__init__.py @@ -17,8 +17,16 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__version__ = "1.1.1" -__all__ = [] +__version__ = "1.2.0" +__vername__ = "更高效的算法管理" __author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon")) +__all__ = [ + # 主要类 + "MidiConvert", + # 附加类 + # "SingleNote", + "SingleCommand", + # "TimeStamp", 未来功能 +] from .main import * diff --git a/Musicreater/exceptions.py b/Musicreater/exceptions.py index 1d62381..53847a2 100644 --- a/Musicreater/exceptions.py +++ b/Musicreater/exceptions.py @@ -17,8 +17,6 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md - - class MSCTBaseException(Exception): """音·创库版本的所有错误均继承于此""" @@ -27,7 +25,7 @@ class MSCTBaseException(Exception): super().__init__(*args) def miao( - self, + self, ): for i in self.args: print(i + "喵!") @@ -52,6 +50,14 @@ class MidiDestroyedError(MSCTBaseException): super().__init__("MIDI文件损坏:无法读取MIDI文件", *args) +class MidiUnboundError(MSCTBaseException): + """未定义Midi对象""" + + def __init__(self, *args): + """未绑定Midi对象""" + super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args) + + class CommandFormatError(RuntimeError): """指令格式与目标格式不匹配而引起的错误""" diff --git a/Musicreater/experiment.py b/Musicreater/experiment.py index 27c0a9b..fa32e6f 100644 --- a/Musicreater/experiment.py +++ b/Musicreater/experiment.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - """ 新版本功能以及即将启用的函数 """ @@ -17,190 +16,478 @@ Terms & Conditions: License.md in the root directory # Email TriM-Organization@hotmail.com # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md +from typing import Dict, List, Tuple, Union from .exceptions import * -from .main import MidiConvert, mido +from .main import MidiConvert from .subclass import * from .utils import * -# 简单的单音填充 -def _toCmdList_m4( - self: MidiConvert, - scoreboard_name: str = "mscplay", - MaxVolume: float = 1.0, - speed: float = 1.0, -) -> list: +class FutureMidiConvertM4(MidiConvert): """ - 使用金羿的转换思路,将midi转换为我的世界命令列表,并使用完全填充算法优化音感 - :param scoreboard_name: 我的世界的计分板名称 - :param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 - :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed - :return: tuple(命令列表, 命令个数, 计分板最大值) + 加入插值算法优化音感 + : 经测试,生成效果已经达到,感觉良好 """ - # TODO: 这里的时间转换不知道有没有问题 - - if speed == 0: - if self.debug_mode: - raise ZeroSpeedError("播放速度仅可为正实数") - speed = 1 - MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) - - # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 - channels = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []] - - # 我们来用通道统计音乐信息 - 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 - except NameError: - raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") - - if msg.is_meta: - if msg.type == "set_tempo": - tempo = msg.tempo - else: - if self.debug_mode: - try: - if msg.channel > 15: - raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") - except AttributeError: - pass - - if msg.type == "program_change": - channels[msg.channel].append(("PgmC", msg.program, microseconds)) - - elif msg.type == "note_on" and msg.velocity != 0: - channels[msg.channel].append( - ("NoteS", msg.note, msg.velocity, microseconds) - ) - - elif (msg.type == "note_on" and msg.velocity == 0) or ( - msg.type == "note_off" - ): - channels[msg.channel].append(("NoteE", msg.note, microseconds)) - - """整合后的音乐通道格式 - 每个通道包括若干消息元素其中逃不过这三种: - - 1 切换乐器消息 - - ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒) - - 2 音符开始消息 - - ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒) - - 3 音符结束消息 - - ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)""" - - note_channels = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []] - - # 此处 我们把通道视为音轨 - for i in range(len(channels)): - # 如果当前通道为空 则跳过 - - noteMsgs = [] - MsgIndex = [] - - for msg in channels[i]: - if msg[0] == "PgmC": - InstID = msg[1] - - elif msg[0] == "NoteS": - noteMsgs.append(msg[1:]) - MsgIndex.append(msg[1]) - - elif msg[0] == "NoteE": - if msg[1] in MsgIndex: - note_channels[i].append( - SingleNote( - InstID, - msg[1], - noteMsgs[MsgIndex.index(msg[1])][1], - noteMsgs[MsgIndex.index(msg[1])][2], - msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2], - ) - ) - noteMsgs.pop(MsgIndex.index(msg[1])) - MsgIndex.pop(MsgIndex.index(msg[1])) - - tracks = [] - cmdAmount = 0 - maxScore = 0 - CheckFirstChannel = False # 临时用的插值计算函数 - def _linearFun(_note: SingleNote) -> list: + @staticmethod + def _linear_note( + _note: SingleNote, + _apply_time_division: int = 100, + ) -> List[Tuple[int, int, int, int, float],]: """传入音符数据,返回以半秒为分割的插值列表 :param _note: SingleNote 音符 :return list[tuple(int开始时间(毫秒), int乐器, int音符, int力度(内置), float音量(播放)),]""" - result = [] + totalCount = int(_note.duration / _apply_time_division) + if totalCount == 0: + return [ + (_note.start_time, _note.inst, _note.pitch, _note.velocity, 1), + ] + # print(totalCount) - totalCount = int(_note.lastTime / 500) + result: List[ + Tuple[int, int, int, int, float], + ] = [] for _i in range(totalCount): result.append( ( - _note.startTime + _i * 500, + _note.start_time + _i * _apply_time_division, _note.instrument, _note.pitch, _note.velocity, - MaxVolume * ((totalCount - _i) / totalCount), + ((totalCount - _i) / totalCount), ) ) return result - # 此处 我们把通道视为音轨 - for track in note_channels: - # 如果当前通道为空 则跳过 - if not track: - continue + # 简单的单音填充 + def to_command_list_in_score( + self, + scoreboard_name: str = "mscplay", + max_volume: float = 1.0, + speed: float = 1.0, + ) -> Tuple[List[List[SingleCommand]], int, int]: + """ + 使用金羿的转换思路,使用完全填充算法优化音感后,将midi转换为我的世界命令列表 - if note_channels.index(track) == 0: - CheckFirstChannel = True - SpecialBits = False - elif note_channels.index(track) == 9: - SpecialBits = True - else: - CheckFirstChannel = False - SpecialBits = False + Parameters + ---------- + scoreboard_name: str + 我的世界的计分板名称 + max_volume: float + 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放 + speed: float + 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed - nowTrack = [] + Returns + ------- + tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 ) + """ - for note in track: - for every_note in _linearFun(note): - # 应该是计算的时候出了点小问题 - # 我们应该用一个MC帧作为时间单位而不是半秒 + if speed == 0: + raise ZeroSpeedError("播放速度仅可为正实数") + max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume) - if SpecialBits: - soundID, _X = self.perc_inst_to_soundID_withX(InstID) - else: - soundID, _X = self.inst_to_souldID_withX(InstID) + self.to_music_channels() - score_now = round(every_note[0] / speed / 50000) + note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[]) + InstID = -1 - maxScore = max(maxScore, score_now) + # 此处 我们把通道视为音轨 + for i in self.channels.keys(): + # 如果当前通道为空 则跳过 + if not self.channels[i]: + continue - nowTrack.append( - "execute @a[scores={" - + str(scoreboard_name) - + "=" - + str(score_now) - + "}" - + f"] ~ ~ ~ playsound {soundID} @s ~ ~{1 / every_note[4] - 1} ~ " - f"{note.velocity * (0.7 if CheckFirstChannel else 0.9)} {2 ** ((note.pitch - 60 - _X) / 12)}" + # nowChannel = [] + for track_no, track in self.channels[i].items(): + + noteMsgs = [] + MsgIndex = [] + + for msg in track: + if msg[0] == "PgmC": + InstID = msg[1] + + elif msg[0] == "NoteS": + noteMsgs.append(msg[1:]) + MsgIndex.append(msg[1]) + + elif msg[0] == "NoteE": + if msg[1] in MsgIndex: + note_channels[i].append( + SingleNote( + InstID, + msg[1], + noteMsgs[MsgIndex.index(msg[1])][1], + noteMsgs[MsgIndex.index(msg[1])][2], + msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2], + track_number=track_no, + ) + ) + noteMsgs.pop(MsgIndex.index(msg[1])) + MsgIndex.pop(MsgIndex.index(msg[1])) + + del InstID + + tracks = [] + cmd_amount = 0 + max_score = 0 + + # 此处 我们把通道视为音轨 + for no, track in note_channels.items(): + # 如果当前通道为空 则跳过 + if not track: + continue + + SpecialBits = True if no == 9 else False + + track_now = [] + + for note in track: + for every_note in self._linear_note( + note, 50 if note.track_no == 0 else 500 + ): + + soundID, _X = ( + self.perc_inst_to_soundID_withX(note.pitch) + if SpecialBits + else self.inst_to_souldID_withX(note.inst) + ) + + score_now = round(every_note[0] / speed / 50) + + max_score = max(max_score, score_now) + mc_pitch = 2 ** ((note.pitch - 60 - _X) / 12) + blockmeter = 1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1 + + track_now.append( + SingleCommand( + self.execute_cmd_head.format( + "@a[scores=({}={})]".format(scoreboard_name, score_now) + .replace("(", r"{") + .replace(")", r"}") + ) + + "playsound {} @s ^ ^ ^{} {} {}".format( + soundID, + blockmeter, + note.velocity / 128, + "" if SpecialBits else mc_pitch, + ), + annotation="在{}播放{}%({}BM)的{}音".format( + mctick2timestr(score_now), + max_volume * 100, + blockmeter, + "{}:{}".format(soundID, note.pitch), + ), + ), + ) + + cmd_amount += 1 + + if track_now: + self.music_command_list.extend(track_now) + tracks.append(track_now) + + self.music_tick_num = max_score + return (tracks, cmd_amount, max_score) + + # 简单的单音填充的延迟应用 + def to_command_list_in_delay( + self, + max_volume: float = 1.0, + speed: float = 1.0, + player_selector: str = "@a", + ) -> Tuple[List[SingleCommand], int]: + """ + 使用金羿的转换思路,使用完全填充算法优化音感后,将midi转换为我的世界命令列表,并输出每个音符之后的延迟 + + Parameters + ---------- + max_volume: float + 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 + speed: float + 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed + player_selector: str + 玩家选择器,默认为`@a` + + Returns + ------- + tuple( list[SingleCommand,...], int音乐时长游戏刻 ) + """ + + if speed == 0: + raise ZeroSpeedError("播放速度仅可为正实数") + max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume) + + self.to_music_channels() + + note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[]) + InstID = -1 + + # 此处 我们把通道视为音轨 + for i in self.channels.keys(): + # 如果当前通道为空 则跳过 + if not self.channels[i]: + continue + + # nowChannel = [] + for track_no, track in self.channels[i].items(): + + noteMsgs = [] + MsgIndex = [] + + for msg in track: + if msg[0] == "PgmC": + InstID = msg[1] + + elif msg[0] == "NoteS": + noteMsgs.append(msg[1:]) + MsgIndex.append(msg[1]) + + elif msg[0] == "NoteE": + if msg[1] in MsgIndex: + note_channels[i].append( + SingleNote( + InstID, + msg[1], + noteMsgs[MsgIndex.index(msg[1])][1], + noteMsgs[MsgIndex.index(msg[1])][2], + msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2], + track_number=track_no, + ) + ) + noteMsgs.pop(MsgIndex.index(msg[1])) + MsgIndex.pop(MsgIndex.index(msg[1])) + + del InstID + + tracks = {} + InstID = -1 + # open("RES.TXT", "w", encoding="utf-8").write(str(note_channels)) + + # 此处 我们把通道视为音轨 + for no, track in note_channels.items(): + # 如果当前通道为空 则跳过 + if not track: + continue + + SpecialBits = True if no == 9 else False + + for note in track: + for every_note in self._linear_note( + note, 50 if note.track_no == 0 else 500 + ): + + soundID, _X = ( + self.perc_inst_to_soundID_withX(note.pitch) + if SpecialBits + else self.inst_to_souldID_withX(note.inst) + ) + + score_now = round(every_note[0] / speed / 50) + + try: + tracks[score_now].append( + self.execute_cmd_head.format(player_selector) + + f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} " + + ( + "" + if SpecialBits + else f"{2 ** ((note.pitch - 60 - _X) / 12)}" + ) + ) + except KeyError: + tracks[score_now] = [ + self.execute_cmd_head.format(player_selector) + + f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} " + + ( + "" + if SpecialBits + else f"{2 ** ((note.pitch - 60 - _X) / 12)}" + ) + ] + + all_ticks = list(tracks.keys()) + all_ticks.sort() + results = [] + + 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 ( + all_ticks[i] - all_ticks[i - 1] + if i != 0 + else all_ticks[i] + ) + ), + annotation="在{}播放{}%的{}音".format( + mctick2timestr(i), max_volume * 100, "" + ), + ) ) - cmdAmount += 1 - tracks.append(nowTrack) + self.music_command_list = results + self.music_tick_num = max(all_ticks) + return results, self.music_tick_num - return [tracks, cmdAmount, maxScore] +class FutureMidiConvertM5(MidiConvert): + """ + 加入同刻偏移算法优化音感 + """ + + # 神奇的偏移音 + def to_command_list_in_delay( + self, + max_volume: float = 1.0, + speed: float = 1.0, + player_selector: str = "@a", + ) -> Tuple[List[SingleCommand], int]: + """ + 使用金羿的转换思路,使用同刻偏移算法优化音感后,将midi转换为我的世界命令列表,并输出每个音符之后的延迟 + + Parameters + ---------- + max_volume: float + 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 + speed: float + 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed + player_selector: str + 玩家选择器,默认为`@a` + + Returns + ------- + tuple( list[SingleCommand,...], int音乐时长游戏刻 ) + """ + + if speed == 0: + raise ZeroSpeedError("播放速度仅可为正实数") + max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume) + + self.to_music_channels() + + tracks = {} + InstID = -1 + + # 此处 我们把通道视为音轨 + for i in self.channels.keys(): + # 如果当前通道为空 则跳过 + if not self.channels[i]: + continue + + # 第十通道是打击乐通道 + SpecialBits = True if i == 9 else False + + # nowChannel = [] + + for track_no, track in self.channels[i].items(): + for msg in track: + if msg[0] == "PgmC": + InstID = msg[1] + + elif msg[0] == "NoteS": + soundID, _X = ( + self.perc_inst_to_soundID_withX(msg[1]) + if SpecialBits + else self.inst_to_souldID_withX(InstID) + ) + + score_now = round(msg[-1] / float(speed) / 50) + # print(score_now) + + try: + tracks[score_now].append( + self.execute_cmd_head.format(player_selector) + + f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} " + + ( + "" + if SpecialBits + else f"{2 ** ((msg[1] - 60 - _X) / 12)}" + ) + ) + except KeyError: + tracks[score_now] = [ + self.execute_cmd_head.format(player_selector) + + f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} " + + ( + "" + if SpecialBits + else f"{2 ** ((msg[1] - 60 - _X) / 12)}" + ) + ] + + all_ticks = list(tracks.keys()) + all_ticks.sort() + results = [] + + 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 ( + (all_ticks[i + 1] - all_ticks[i]) + / len(tracks[all_ticks[i]]) + < 1 + ) + else 1 + ) + if j != 0 + else ( + ( + all_ticks[i] + - all_ticks[i - 1] + - ( + 0 + if ( + (all_ticks[i] - all_ticks[i - 1]) + / len(tracks[all_ticks[i - 1]]) + < 1 + ) + else (len(tracks[all_ticks[i - 1]]) - 1) + ) + ) + if i != 0 + else all_ticks[i] + ) + ), + annotation="在{}播放{}%的{}音".format( + mctick2timestr( + i + 0 + if ( + (all_ticks[i + 1] - all_ticks[i]) + / len(tracks[all_ticks[i]]) + < 1 + ) + else j + ), + max_volume * 100, + "", + ), + ) + ) + + self.music_command_list = results + self.music_tick_num = max(all_ticks) + return results, self.music_tick_num + + +class FutureMidiConvertM6(MidiConvert): + """ + 加入插值算法优化音感,但仅用于第一音轨 + """ + + # TODO 没写完的!!!! diff --git a/Musicreater/main.py b/Musicreater/main.py index 188a980..3dbea68 100644 --- a/Musicreater/main.py +++ b/Musicreater/main.py @@ -19,22 +19,48 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md -import os +# BUG退散!BUG退散! BUG退散!BUG退散! +# 异常、错误作乱之时 異常、誤りが、困った時は +# 二六字组!万国码合!二六字组!万国码合! グループ!コード#!グループ!コード#! +# 赶快呼叫 程序员!Let's Go! 直ぐに呼びましょプログラマ レッツゴー! + + + import math -from typing import Tuple, List, Union +import os +from typing import List, Literal, Tuple, Union import mido -from .exceptions import * from .constants import * -from .utils import * +from .exceptions import * from .subclass import * +from .utils import * -VM = TypeVar("VM", mido.MidiFile, None) # void mido +VoidMido = Union[mido.MidiFile, None] # void mido """ 空Midi类类型 """ +ChannelType = Dict[ + int, + Dict[ + int, + List[ + Union[ + Tuple[Literal["PgmC"], int, int], + Tuple[Literal["NoteS"], int, int, int], + Tuple[Literal["NoteE"], int, int], + ] + ], + ], +] +""" +以字典所标记的频道信息类型 + +Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],] +""" + """ 学习笔记: tempo: microseconds per quarter note 毫秒每四分音符,换句话说就是一拍占多少毫秒 @@ -75,7 +101,7 @@ class MidiConvert: 将Midi文件转换为我的世界内容 """ - midi: VM + midi: VoidMido """MidiFile对象""" midi_music_name: str @@ -87,7 +113,7 @@ class MidiConvert: execute_cmd_head: str """execute指令头部""" - channels: Dict[int, Dict[int, List[Tuple[str, int, int, Union[None, int]]]]] + channels: ChannelType """频道信息字典""" music_command_list: List[SingleCommand] @@ -101,7 +127,7 @@ class MidiConvert: def __init__( self, - midi_obj: VM, + midi_obj: VoidMido, midi_name: str, enable_old_exe_format: bool = False, ): @@ -118,7 +144,7 @@ class MidiConvert: 是否启用旧版(≤1.19)指令格式,默认为否 """ - self.midi: VM = midi_obj + self.midi: VoidMido = midi_obj self.midi_music_name: str = midi_name @@ -151,7 +177,9 @@ class MidiConvert: 是否启用旧版(≤1.19)指令格式,默认为否 """ - midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0].replace(' ','_') + midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0].replace( + " ", "_" + ) """文件名,不含路径且不含后缀""" try: @@ -491,48 +519,43 @@ class MidiConvert: def to_music_channels( self, - ) -> Dict[int, Dict[int, List[Tuple[str, int, int, Union[None, int]]]]]: + ) -> ChannelType: """ - 使用金羿的转换思路,将midi解析并转换为频道信息 + 使用金羿的转换思路,将midi解析并转换为频道信息字典 Returns ------- - Dict[int, Dict[int, List[Tuple[str,int,int,Union[None,int]]]]] + 以频道作为分割的Midi信息字典: + Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],] """ + if self.midi is None: + raise MidiUnboundError( + "你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。" + ) # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 - midi_channels = empty_midi_channels() + midi_channels: ChannelType = empty_midi_channels() + tempo = mido.midifiles.midifiles.DEFAULT_TEMPO + # a = 0 # 我们来用通道统计音乐信息 # 但是是用分轨的思路的 for track_no, track in enumerate(self.midi.tracks): + # print(track_no,track) microseconds = 0 + if not track: + continue + # print(track_no,"="*20) for msg in track: + # print("+++",msg) if msg.time != 0: - try: - microseconds += ( - msg.time * tempo / self.midi.ticks_per_beat / 1000 - ) - # print(microseconds) - except NameError: - # raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") - microseconds += ( - msg.time - * mido.midifiles.midifiles.DEFAULT_TEMPO - / self.midi.ticks_per_beat - ) / 1000 + microseconds += msg.time * tempo / self.midi.ticks_per_beat / 1000 if msg.is_meta: if msg.type == "set_tempo": tempo = msg.tempo else: - # 曾用于调试模式 - # try: - # if msg.channel > 15: - # raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") - # except AttributeError: - # pass if not track_no in midi_channels[msg.channel].keys(): midi_channels[msg.channel][track_no] = [] @@ -545,6 +568,7 @@ class MidiConvert: midi_channels[msg.channel][track_no].append( ("NoteS", msg.note, msg.velocity, microseconds) ) + # a+=1 elif (msg.type == "note_on" and msg.velocity == 0) or ( msg.type == "note_off" @@ -553,6 +577,8 @@ class MidiConvert: ("NoteE", msg.note, microseconds) ) + # print(a) + """整合后的音乐通道格式 每个通道包括若干消息元素其中逃不过这三种: @@ -563,10 +589,11 @@ class MidiConvert: ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒) 3 音符结束消息 - ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)""" - + ("NoteE", 结束的音符ID, 距离演奏开始的毫秒)""" + del tempo, self.channels self.channels = midi_channels - return self.channels + # [print([print(no,tno,sum([True if i[0] == 'NoteS' else False for i in track])) for tno,track in cna.items()]) if cna else False for no,cna in midi_channels.items()] + return midi_channels def to_command_list_in_score( self, @@ -598,6 +625,7 @@ class MidiConvert: tracks = [] cmdAmount = 0 maxScore = 0 + InstID = -1 self.to_music_channels() @@ -620,21 +648,14 @@ class MidiConvert: InstID = msg[1] elif msg[0] == "NoteS": - try: - soundID, _X = ( - self.perc_inst_to_soundID_withX(InstID) - if SpecialBits - else self.inst_to_souldID_withX(InstID) - ) - except UnboundLocalError as E: - # raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") - soundID, _X = ( - self.perc_inst_to_soundID_withX(-1) - if SpecialBits - else self.inst_to_souldID_withX(-1) - ) + soundID, _X = ( + self.perc_inst_to_soundID_withX(msg[1]) + if SpecialBits + else self.inst_to_souldID_withX(InstID) + ) score_now = round(msg[-1] / float(speed) / 50) maxScore = max(maxScore, score_now) + mc_pitch = "" if SpecialBits else 2 ** ((msg[1] - 60 - _X) / 12) nowTrack.append( SingleCommand( @@ -645,10 +666,13 @@ class MidiConvert: .replace("(", r"{") .replace(")", r"}") ) - + f"playsound {soundID} @s ^ ^ ^{1 / max_volume - 1} {msg[2] / 128} " - f"{2 ** ((msg[1] - 60 - _X) / 12)}", + + "playsound {} @s ^ ^ ^{} {} {}".format( + soundID, 1 / max_volume - 1, msg[2] / 128, mc_pitch + ), annotation="在{}播放{}%的{}音".format( - mctick2timestr(score_now), max_volume * 100, "" + mctick2timestr(score_now), + max_volume * 100, + "{}:{}".format(soundID, mc_pitch), ), ), ) @@ -659,6 +683,8 @@ class MidiConvert: self.music_command_list.extend(nowTrack) tracks.append(nowTrack) + # print(cmdAmount) + del InstID self.music_tick_num = maxScore return (tracks, cmdAmount, maxScore) @@ -692,6 +718,8 @@ class MidiConvert: self.to_music_channels() tracks = {} + InstID = -1 + # cmd_amount = 0 # 此处 我们把通道视为音轨 for i in self.channels.keys(): @@ -710,21 +738,13 @@ class MidiConvert: InstID = msg[1] elif msg[0] == "NoteS": - try: - soundID, _X = ( - self.perc_inst_to_soundID_withX(msg[1]) - if SpecialBits - else self.inst_to_souldID_withX(InstID) - ) - except UnboundLocalError as E: - # raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}") - soundID, _X = ( - self.perc_inst_to_soundID_withX(-1) - if SpecialBits - else self.inst_to_souldID_withX(-1) - ) + soundID, _X = ( + self.perc_inst_to_soundID_withX(msg[1]) + if SpecialBits + else self.inst_to_souldID_withX(InstID) + ) + score_now = round(msg[-1] / float(speed) / 50) - # print(score_now) try: tracks[score_now].append( @@ -747,6 +767,11 @@ class MidiConvert: ) ] + # cmd_amount += 1 + + # print(cmd_amount) + + del InstID all_ticks = list(tracks.keys()) all_ticks.sort() results = [] @@ -773,83 +798,7 @@ class MidiConvert: self.music_command_list = results self.music_tick_num = max(all_ticks) - return [results, self.music_tick_num] - - - def to_dict( - self, - ) -> dict: - """ - 使用金羿的转换思路,将midi转换为字典 - :return: dict() - """ - - # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 - channels = empty_midi_channels() - - # 我们来用通道统计音乐信息 - # 但是是用分轨的思路的 - 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 - ) - # print(microseconds) - except NameError: - # 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 - else: - try: - # 曾用于调试模式 - # 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: - pass - - if msg.type == "program_change": - 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( - ("NoteS", msg.note, msg.velocity, microseconds) - ) - - elif (msg.type == "note_on" and msg.velocity == 0) or ( - msg.type == "note_off" - ): - channels[msg.channel][track_no].append( - ("NoteE", msg.note, microseconds) - ) - - """整合后的音乐通道格式 - 每个通道包括若干消息元素其中逃不过这三种: - - 1 切换乐器消息 - ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒) - - 2 音符开始消息 - ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒) - - 3 音符结束消息 - ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)""" - - return channels + return results, self.music_tick_num def copy_important(self): dst = MidiConvert( diff --git a/Musicreater/plugin/__init__.py b/Musicreater/plugin/__init__.py index 50d7b1c..a6cfdd6 100644 --- a/Musicreater/plugin/__init__.py +++ b/Musicreater/plugin/__init__.py @@ -14,7 +14,9 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__all__ = [] +__all__ = [ + "ConvertConfig", +] __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 76ff3fe..a44182c 100644 --- a/Musicreater/plugin/archive.py +++ b/Musicreater/plugin/archive.py @@ -20,7 +20,8 @@ Terms & Conditions: License.md in the root directory import os import uuid import zipfile -from typing import List +import datetime +from typing import List, Union, Literal def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None): @@ -46,16 +47,23 @@ def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None): def behavior_mcpack_manifest( pack_description: str = "", - pack_version: List[int] = [0, 0, 1], + pack_version: Union[List[int], Literal[None]] = None, pack_name: str = "", - pack_uuid: str = None, + pack_uuid: Union[str, Literal[None]] = None, modules_description: str = "", modules_version: List[int] = [0, 0, 1], - modules_uuid: str = None, + modules_uuid: Union[str, Literal[None]] = None, ): """ 生成一个我的世界行为包组件的定义清单文件 """ + if not pack_version: + now_date = datetime.datetime.now() + pack_version = [ + now_date.year, + now_date.month * 100 + now_date.day, + now_date.hour * 100 + now_date.minute, + ] return { "format_version": 1, "header": { diff --git a/Musicreater/plugin/bdxfile/__init__.py b/Musicreater/plugin/bdxfile/__init__.py index c59dcff..3823a79 100644 --- a/Musicreater/plugin/bdxfile/__init__.py +++ b/Musicreater/plugin/bdxfile/__init__.py @@ -14,7 +14,10 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__all__ = [] +__all__ = [ + "to_BDX_file_in_score", + "to_BDX_file_in_delay" +] __author__ = (("金羿", "Eilles Wan"),) from .main import * diff --git a/Musicreater/plugin/bdxfile/main.py b/Musicreater/plugin/bdxfile/main.py index a60d795..7d7694c 100644 --- a/Musicreater/plugin/bdxfile/main.py +++ b/Musicreater/plugin/bdxfile/main.py @@ -13,12 +13,14 @@ Terms & Conditions: License.md in the root directory 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 +from ..bdx import (bdx_move, commands_to_BDX_bytes, + form_command_block_in_BDX_bytes, x, y, z) +from ..main import ConvertConfig def to_BDX_file_in_score( @@ -142,7 +144,7 @@ def to_BDX_file_in_delay( 作者名称 max_height: int 生成结构最大高度 - + Returns ------- tuple[int指令数量, int音乐总延迟, tuple[int,]结构大小, tuple[int,]终点坐标] @@ -169,7 +171,6 @@ def to_BDX_file_in_delay( 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: diff --git a/Musicreater/plugin/funcpack/__init__.py b/Musicreater/plugin/funcpack/__init__.py index 76e4e9c..a6e3456 100644 --- a/Musicreater/plugin/funcpack/__init__.py +++ b/Musicreater/plugin/funcpack/__init__.py @@ -14,7 +14,9 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__all__ = [] +__all__ = [ + "to_function_addon_in_score" +] __author__ = (("金羿", "Eilles Wan"),) from .main import * diff --git a/Musicreater/plugin/funcpack/main.py b/Musicreater/plugin/funcpack/main.py index 5478e4c..43ac617 100644 --- a/Musicreater/plugin/funcpack/main.py +++ b/Musicreater/plugin/funcpack/main.py @@ -56,13 +56,12 @@ def to_function_addon_in_score( shutil.rmtree(f"{data_cfg.dist_path}/temp/functions/") os.makedirs(f"{data_cfg.dist_path}/temp/functions/mscplay") - # 写入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"{midi_cvt.midi_music_name} 音乐播放包,MCFUNCTION(MCPACK) 计分播放器 - 由 音·创 生成", - pack_name=midi_cvt.midi_music_name+"播放", + pack_name=midi_cvt.midi_music_name + "播放", modules_description=f"无 - 由 音·创 生成", ), fp=f, diff --git a/Musicreater/plugin/main.py b/Musicreater/plugin/main.py index 3bca4c1..dcdbf39 100644 --- a/Musicreater/plugin/main.py +++ b/Musicreater/plugin/main.py @@ -17,7 +17,7 @@ Terms & Conditions: License.md in the root directory from dataclasses import dataclass -from typing import Tuple, Union +from typing import Tuple, Union, Literal from ..constants import DEFAULT_PROGRESSBAR_STYLE @@ -34,7 +34,7 @@ class ConvertConfig: speed_multiplier: float """速度倍率""" - progressbar_style: Tuple[str, Tuple[str, str]] + progressbar_style: Union[Tuple[str, Tuple[str, str]], Literal[None]] """进度条样式组""" dist_path: str diff --git a/Musicreater/plugin/mcstructfile/__init__.py b/Musicreater/plugin/mcstructfile/__init__.py index 72480e1..80f445b 100644 --- a/Musicreater/plugin/mcstructfile/__init__.py +++ b/Musicreater/plugin/mcstructfile/__init__.py @@ -14,7 +14,9 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__all__ = [] +__all__ = [ + "to_mcstructure_file_in_delay" +] __author__ = (("金羿", "Eilles Wan"),) from .main import * diff --git a/Musicreater/plugin/mcstructfile/main.py b/Musicreater/plugin/mcstructfile/main.py index a11b784..3541793 100644 --- a/Musicreater/plugin/mcstructfile/main.py +++ b/Musicreater/plugin/mcstructfile/main.py @@ -71,5 +71,6 @@ def to_mcstructure_file_in_delay( def to_mcstructure_file_in_redstone( midi_cvt: MidiConvert, - data_cfg: ConvertConfig,): - pass \ No newline at end of file + data_cfg: ConvertConfig, +): + pass diff --git a/Musicreater/plugin/mcstructpack/__init__.py b/Musicreater/plugin/mcstructpack/__init__.py index 1073a36..2b5c027 100644 --- a/Musicreater/plugin/mcstructpack/__init__.py +++ b/Musicreater/plugin/mcstructpack/__init__.py @@ -14,7 +14,9 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__all__ = [] +__all__ = [ + "to_mcstructure_addon_in_delay" +] __author__ = (("金羿", "Eilles Wan"),) from .main import * diff --git a/Musicreater/plugin/mcstructpack/main.py b/Musicreater/plugin/mcstructpack/main.py index b9e1bfb..9e45a5f 100644 --- a/Musicreater/plugin/mcstructpack/main.py +++ b/Musicreater/plugin/mcstructpack/main.py @@ -21,8 +21,7 @@ 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) +from ..mcstructure import commands_to_structure, form_command_block_in_NBT_struct def to_mcstructure_addon_in_delay( @@ -73,7 +72,7 @@ def to_mcstructure_addon_in_delay( json.dump( behavior_mcpack_manifest( pack_description=f"{midi_cvt.midi_music_name} 音乐播放包,MCSTRUCTURE(MCPACK) 延迟播放器 - 由 音·创 生成", - pack_name=midi_cvt.midi_music_name+"播放", + pack_name=midi_cvt.midi_music_name + "播放", modules_description=f"无 - 由 音·创 生成", ), fp=f, diff --git a/Musicreater/plugin/mcstructure.py b/Musicreater/plugin/mcstructure.py index d7f4be1..1828e79 100644 --- a/Musicreater/plugin/mcstructure.py +++ b/Musicreater/plugin/mcstructure.py @@ -16,7 +16,7 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -from typing import List +from typing import List, Tuple from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long @@ -25,7 +25,10 @@ from .common import bottem_side_length_of_smallest_square_bottom_box def form_note_block_in_NBT_struct( - note: int, coordinate: tuple, instrument: str = "note.harp", powered: bool = False + note: int, + coordinate: Tuple[int, int, int], + instrument: str = "note.harp", + powered: bool = False, ): """生成音符盒方块 :param note: `int`(0~24) @@ -54,7 +57,7 @@ def form_note_block_in_NBT_struct( "x": coordinate[0], "y": coordinate[1], "z": coordinate[2], - } + } # type: ignore }, ) @@ -162,7 +165,7 @@ def form_command_block_in_NBT_struct( "x": coordinate[0], "y": coordinate[1], "z": coordinate[2], - } + } # type: ignore }, compability_version=17959425, ) diff --git a/Musicreater/previous.py b/Musicreater/previous.py new file mode 100644 index 0000000..f2aeb38 --- /dev/null +++ b/Musicreater/previous.py @@ -0,0 +1,326 @@ +# -*- 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 typing import Dict, List, Tuple, Union + +from .exceptions import * +from .main import MidiConvert, mido +from .subclass import * +from .utils import * + + +class ObsoleteMidiConvert(MidiConvert): + """ + 我说一句话: + 这些破烂老代码能跑得起来就是谢天谢地,你们还指望我怎么样?这玩意真的不会再维护了,我发誓! + """ + + def to_command_list_method1( + self, + scoreboard_name: str = "mscplay", + MaxVolume: float = 1.0, + speed: float = 1.0, + ) -> list: + """ + 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表 + :param scoreboard_name: 我的世界的计分板名称 + :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed + :return: tuple(命令列表, 命令个数, 计分板最大值) + """ + # :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 + tracks = [] + if speed == 0: + raise ZeroSpeedError("播放速度仅可为正实数") + if not self.midi: + raise MidiUnboundError( + "你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。" + ) + + MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) + commands = 0 + maxscore = 0 + tempo = mido.midifiles.midifiles.DEFAULT_TEMPO + + # 分轨的思路其实并不好,但这个算法就是这样 + # 所以我建议用第二个方法 _toCmdList_m2 + for i, track in enumerate(self.midi.tracks): + ticks = 0 + instrumentID = 0 + singleTrack = [] + + for msg in track: + ticks += msg.time + if msg.is_meta: + if msg.type == "set_tempo": + tempo = msg.tempo + else: + if msg.type == "program_change": + instrumentID = msg.program + + if msg.type == "note_on" and msg.velocity != 0: + nowscore = round( + (ticks * tempo) + / ((self.midi.ticks_per_beat * float(speed)) * 50000) + ) + maxscore = max(maxscore, nowscore) + if msg.channel == 9: + soundID, _X = self.perc_inst_to_soundID_withX(instrumentID) + else: + soundID, _X = self.inst_to_souldID_withX(instrumentID) + + singleTrack.append( + "execute @a[scores={" + + str(scoreboard_name) + + "=" + + str(nowscore) + + "}" + + f"] ~ ~ ~ playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} " + f"{2 ** ((msg.note - 60 - _X) / 12)}" + ) + commands += 1 + + if len(singleTrack) != 0: + tracks.append(singleTrack) + + return [tracks, commands, maxscore] + + # 原本这个算法的转换效果应该和上面的算法相似的 + def _toCmdList_m2( + self: MidiConvert, + scoreboard_name: str = "mscplay", + MaxVolume: float = 1.0, + speed: float = 1.0, + ) -> tuple: + """ + 使用神羽和金羿的转换思路,将midi转换为我的世界命令列表 + :param scoreboard_name: 我的世界的计分板名称 + :param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 + :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed + :return: tuple(命令列表, 命令个数, 计分板最大值) + """ + + if speed == 0: + raise ZeroSpeedError("播放速度仅可为正实数") + MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) + + tracks = [] + cmdAmount = 0 + maxScore = 0 + InstID = -1 + + self.to_music_channels() + + # 此处 我们把通道视为音轨 + for i in self.channels.keys(): + # 如果当前通道为空 则跳过 + if not self.channels[i]: + continue + + if i == 9: + SpecialBits = True + else: + SpecialBits = False + + nowTrack = [] + + for track_no, track in self.channels[i].items(): + for msg in track: + if msg[0] == "PgmC": + InstID = msg[1] + + elif msg[0] == "NoteS": + soundID, _X = ( + self.perc_inst_to_soundID_withX(msg[1]) + if SpecialBits + else self.inst_to_souldID_withX(InstID) + ) + 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 / MaxVolume - 1} {msg[2] / 128} " + f"{2 ** ((msg[1] - 60 - _X) / 12)}" + ) + + cmdAmount += 1 + + if nowTrack: + tracks.append(nowTrack) + + return tracks, cmdAmount, maxScore + + def _toCmdList_withDelay_m1( + self: MidiConvert, + MaxVolume: float = 1.0, + speed: float = 1.0, + player: str = "@a", + ) -> list: + """ + 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟 + :param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 + :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed + :param player: 玩家选择器,默认为`@a` + :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...] + """ + tracks = {} + + if speed == 0: + raise ZeroSpeedError("播放速度仅可为正实数") + if not self.midi: + raise MidiUnboundError( + "你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。" + ) + + MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) + tempo = mido.midifiles.midifiles.DEFAULT_TEMPO + + for i, track in enumerate(self.midi.tracks): + instrumentID = 0 + ticks = 0 + + for msg in track: + ticks += msg.time + if msg.is_meta: + if msg.type == "set_tempo": + tempo = msg.tempo + else: + if msg.type == "program_change": + instrumentID = msg.program + if msg.type == "note_on" and msg.velocity != 0: + now_tick = round( + (ticks * tempo) + / ((self.midi.ticks_per_beat * float(speed)) * 50000) + ) + soundID, _X = self.inst_to_souldID_withX(instrumentID) + try: + tracks[now_tick].append( + 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.execute_cmd_head.format(player) + + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} " + f"{2 ** ((msg.note - 60 - _X) / 12)}" + ] + + results = [] + + all_ticks = list(tracks.keys()) + all_ticks.sort() + + for i in range(len(all_ticks)): + if i != 0: + for j in range(len(tracks[all_ticks[i]])): + if j != 0: + results.append((tracks[all_ticks[i]][j], 0)) + else: + results.append( + (tracks[all_ticks[i]][j], all_ticks[i] - all_ticks[i - 1]) + ) + else: + for j in range(len(tracks[all_ticks[i]])): + results.append((tracks[all_ticks[i]][j], all_ticks[i])) + + return [results, max(all_ticks)] + + def _toCmdList_withDelay_m2( + self: MidiConvert, + MaxVolume: float = 1.0, + speed: float = 1.0, + player: str = "@a", + ) -> list: + """ + 使用神羽和金羿的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟 + :param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 + :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed + :param player: 玩家选择器,默认为`@a` + :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...] + """ + tracks = {} + if speed == 0: + raise ZeroSpeedError("播放速度仅可为正实数") + + MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) + InstID = -1 + self.to_music_channels() + + results = [] + + for i in self.channels.keys(): + # 如果当前通道为空 则跳过 + if not self.channels[i]: + continue + + if i == 9: + SpecialBits = True + else: + SpecialBits = False + + for track_no, track in self.channels[i].items(): + for msg in track: + if msg[0] == "PgmC": + InstID = msg[1] + + elif msg[0] == "NoteS": + soundID, _X = ( + self.perc_inst_to_soundID_withX(msg[1]) + if SpecialBits + else self.inst_to_souldID_withX(InstID) + ) + score_now = round(msg[-1] / float(speed) / 50) + + try: + tracks[score_now].append( + 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.execute_cmd_head.format(player) + + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} " + f"{2 ** ((msg[1] - 60 - _X) / 12)}" + ] + + all_ticks = list(tracks.keys()) + all_ticks.sort() + + for i in range(len(all_ticks)): + for j in range(len(tracks[all_ticks[i]])): + 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] + ) + ), + ) + ) + + return [results, max(all_ticks)] diff --git a/Musicreater/previous/convert.py b/Musicreater/previous/convert.py deleted file mode 100644 index d26f6b8..0000000 --- a/Musicreater/previous/convert.py +++ /dev/null @@ -1,463 +0,0 @@ -# -*- 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 - - -def to_command_list_method1( - self: MidiConvert, - scoreboard_name: str = "mscplay", - MaxVolume: float = 1.0, - speed: float = 1.0, -) -> list: - """ - 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表 - :param scoreboard_name: 我的世界的计分板名称 - :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed - :return: tuple(命令列表, 命令个数, 计分板最大值) - """ - # :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 - tracks = [] - if speed == 0: - if self.debug_mode: - raise ZeroSpeedError("播放速度仅可为正实数") - speed = 1 - MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume) - - commands = 0 - maxscore = 0 - - # 分轨的思路其实并不好,但这个算法就是这样 - # 所以我建议用第二个方法 _toCmdList_m2 - for i, track in enumerate(self.midi.tracks): - ticks = 0 - instrumentID = 0 - singleTrack = [] - - for msg in track: - ticks += msg.time - if msg.is_meta: - if msg.type == "set_tempo": - tempo = msg.tempo - else: - if msg.type == "program_change": - instrumentID = msg.program - - if msg.type == "note_on" and msg.velocity != 0: - try: - nowscore = round( - (ticks * tempo) - / ((self.midi.ticks_per_beat * float(speed)) * 50000) - ) - except NameError: - raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") - maxscore = max(maxscore, nowscore) - if msg.channel == 9: - soundID, _X = self.perc_inst_to_soundID_withX(instrumentID) - else: - soundID, _X = self.inst_to_souldID_withX(instrumentID) - - singleTrack.append( - "execute @a[scores={" - + str(scoreboard_name) - + "=" - + str(nowscore) - + "}" - + f"] ~ ~ ~ playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} " - f"{2 ** ((msg.note - 60 - _X) / 12)}" - ) - commands += 1 - if len(singleTrack) != 0: - tracks.append(singleTrack) - - return [tracks, commands, maxscore] - - -# 原本这个算法的转换效果应该和上面的算法相似的 -def _toCmdList_m2( - self: MidiConvert, - scoreboard_name: str = "mscplay", - MaxVolume: float = 1.0, - speed: float = 1.0, -) -> list: - """ - 使用神羽和金羿的转换思路,将midi转换为我的世界命令列表 - :param scoreboard_name: 我的世界的计分板名称 - :param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 - :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed - :return: tuple(命令列表, 命令个数, 计分板最大值) - """ - - if speed == 0: - 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: [], - } - - microseconds = 0 - - # 我们来用通道统计音乐信息 - for msg in self.midi: - microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样 - if not msg.is_meta: - if self.debug_mode: - try: - if msg.channel > 15: - raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") - except AttributeError: - pass - - if msg.type == "program_change": - channels[msg.channel].append(("PgmC", msg.program, microseconds)) - - elif msg.type == "note_on" and msg.velocity != 0: - channels[msg.channel].append( - ("NoteS", msg.note, msg.velocity, microseconds) - ) - - elif (msg.type == "note_on" and msg.velocity == 0) or ( - msg.type == "note_off" - ): - channels[msg.channel].append(("NoteE", msg.note, microseconds)) - - """整合后的音乐通道格式 - 每个通道包括若干消息元素其中逃不过这三种: - - 1 切换乐器消息 - ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒) - - 2 音符开始消息 - ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒) - - 3 音符结束消息 - ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)""" - - tracks = [] - cmdAmount = 0 - maxScore = 0 - - # 此处 我们把通道视为音轨 - for i in channels.keys(): - # 如果当前通道为空 则跳过 - if not channels[i]: - continue - - if i == 9: - SpecialBits = True - else: - SpecialBits = False - - nowTrack = [] - - for msg in channels[i]: - if msg[0] == "PgmC": - InstID = msg[1] - - elif msg[0] == "NoteS": - try: - soundID, _X = ( - self.perc_inst_to_soundID_withX(InstID) - if SpecialBits - 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) - ) - 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 / MaxVolume - 1} {msg[2] / 128} " - f"{2 ** ((msg[1] - 60 - _X) / 12)}" - ) - - cmdAmount += 1 - - if nowTrack: - tracks.append(nowTrack) - - return [tracks, cmdAmount, maxScore] - - -def _toCmdList_withDelay_m1( - self: MidiConvert, - MaxVolume: float = 1.0, - speed: float = 1.0, - player: str = "@a", -) -> list: - """ - 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟 - :param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 - :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed - :param player: 玩家选择器,默认为`@a` - :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...] - """ - tracks = {} - - if speed == 0: - 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 - - for msg in track: - ticks += msg.time - if msg.is_meta: - if msg.type == "set_tempo": - tempo = msg.tempo - else: - if msg.type == "program_change": - instrumentID = msg.program - if msg.type == "note_on" and msg.velocity != 0: - now_tick = round( - (ticks * tempo) - / ((self.midi.ticks_per_beat * float(speed)) * 50000) - ) - soundID, _X = self.inst_to_souldID_withX(instrumentID) - try: - tracks[now_tick].append( - 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.execute_cmd_head.format(player) - + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} " - f"{2 ** ((msg.note - 60 - _X) / 12)}" - ] - - results = [] - - all_ticks = list(tracks.keys()) - all_ticks.sort() - - for i in range(len(all_ticks)): - if i != 0: - for j in range(len(tracks[all_ticks[i]])): - if j != 0: - results.append((tracks[all_ticks[i]][j], 0)) - else: - results.append( - (tracks[all_ticks[i]][j], all_ticks[i] - all_ticks[i - 1]) - ) - else: - for j in range(len(tracks[all_ticks[i]])): - results.append((tracks[all_ticks[i]][j], all_ticks[i])) - - return [results, max(all_ticks)] - - -def _toCmdList_withDelay_m2( - self: MidiConvert, - MaxVolume: float = 1.0, - speed: float = 1.0, - player: str = "@a", -) -> list: - """ - 使用神羽和金羿的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟 - :param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 - :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed - :param player: 玩家选择器,默认为`@a` - :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...] - """ - tracks = {} - if speed == 0: - 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: [], - } - - microseconds = 0 - - # 我们来用通道统计音乐信息 - for msg in self.midi: - try: - microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样 - - # print(microseconds) - except NameError: - if self.debug_mode: - raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") - else: - microseconds += ( - msg.time * 1000 - ) # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样 - - if msg.is_meta: - if msg.type == "set_tempo": - tempo = msg.tempo - else: - if self.debug_mode: - try: - if msg.channel > 15: - raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)") - except AttributeError: - pass - - if msg.type == "program_change": - channels[msg.channel].append(("PgmC", msg.program, microseconds)) - - elif msg.type == "note_on" and msg.velocity != 0: - channels[msg.channel].append( - ("NoteS", msg.note, msg.velocity, microseconds) - ) - - elif (msg.type == "note_on" and msg.velocity == 0) or ( - msg.type == "note_off" - ): - channels[msg.channel].append(("NoteE", msg.note, microseconds)) - - """整合后的音乐通道格式 - 每个通道包括若干消息元素其中逃不过这三种: - - 1 切换乐器消息 - ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒) - - 2 音符开始消息 - ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒) - - 3 音符结束消息 - ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)""" - - results = [] - - for i in channels.keys(): - # 如果当前通道为空 则跳过 - if not channels[i]: - continue - - if i == 9: - SpecialBits = True - else: - SpecialBits = False - - for msg in channels[i]: - if msg[0] == "PgmC": - InstID = msg[1] - - elif msg[0] == "NoteS": - try: - soundID, _X = ( - self.perc_inst_to_soundID_withX(InstID) - if SpecialBits - 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) - ) - score_now = round(msg[-1] / float(speed) / 50) - - try: - tracks[score_now].append( - 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.execute_cmd_head.format(player) - + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} " - f"{2 ** ((msg[1] - 60 - _X) / 12)}" - ] - - all_ticks = list(tracks.keys()) - all_ticks.sort() - - for i in range(len(all_ticks)): - for j in range(len(tracks[all_ticks[i]])): - 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] - ) - ), - ) - ) - - return [results, max(all_ticks)] diff --git a/Musicreater/subclass.py b/Musicreater/subclass.py index 29f3874..baea371 100644 --- a/Musicreater/subclass.py +++ b/Musicreater/subclass.py @@ -18,9 +18,6 @@ Terms & Conditions: License.md in the root directory from dataclasses import dataclass -from typing import TypeVar - -T = TypeVar("T") # Declare type variable @dataclass(init=False) @@ -36,14 +33,17 @@ class SingleNote: velocity: int """力度/响度""" - startTime: int + start_time: int """开始之时 ms""" - lastTime: int + duration: int """音符持续时间 ms""" + track_no: int + """音符所处的音轨""" + def __init__( - self, instrument: int, pitch: int, velocity: int, startTime: int, lastTime: int + self, instrument: int, pitch: int, velocity: int, startTime: int, lastTime: int, track_number:int = 0 ): """用于存储单个音符的类 :param instrument 乐器编号 @@ -58,10 +58,12 @@ class SingleNote: """音符编号""" self.velocity: int = velocity """力度/响度""" - self.startTime: int = startTime + self.start_time: int = startTime """开始之时 ms""" - self.lastTime: int = lastTime + self.duration: int = lastTime """音符持续时间 ms""" + self.track_no: int = track_number + """音符所处的音轨""" @property def inst(self): @@ -80,19 +82,19 @@ class SingleNote: def __str__(self): return ( f"Note(inst = {self.inst}, pitch = {self.note}, velocity = {self.velocity}, " - f"startTime = {self.startTime}, lastTime = {self.lastTime}, )" + f"startTime = {self.start_time}, lastTime = {self.duration}, )" ) def __tuple__(self): - return self.inst, self.note, self.velocity, self.startTime, self.lastTime + return self.inst, self.note, self.velocity, self.start_time, self.duration def __dict__(self): return { "inst": self.inst, "pitch": self.note, "velocity": self.velocity, - "startTime": self.startTime, - "lastTime": self.lastTime, + "startTime": self.start_time, + "lastTime": self.duration, } @@ -168,18 +170,3 @@ class SingleCommand: if not isinstance(other, self.__class__): return False return self.__str__() == other.__str__() - - -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__() diff --git a/Musicreater/utils.py b/Musicreater/utils.py index eb31735..001ff5b 100644 --- a/Musicreater/utils.py +++ b/Musicreater/utils.py @@ -15,7 +15,8 @@ Terms & Conditions: License.md in the root directory # Email TriM-Organization@hotmail.com # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -from typing import Dict +from typing import Any, Dict + def mctick2timestr(mc_tick: int) -> str: """ @@ -24,8 +25,15 @@ 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[int, Dict]: +def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]: """ 空MIDI通道字典 """ - return dict((i, {}) for i in range(channel_count)) + + return dict( + ( + i, + (staff.copy() if isinstance(staff, (dict, list)) else staff), + ) # 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了 + for i in range(channel_count) + ) diff --git a/docs/库的生成与功能文档.md b/docs/库的生成与功能文档.md index 0d152e5..2efb145 100644 --- a/docs/库的生成与功能文档.md +++ b/docs/库的生成与功能文档.md @@ -203,7 +203,7 @@ print(convertion_result) 在**音·创**中,用来达到这种效果的指令是这样的: ```mcfunction - execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ~ ~Ht ~ Vlct Ptc + execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc ``` |参数|说明|备注| @@ -224,7 +224,7 @@ print(convertion_result) 在音·创中,由于此方式播放的音乐不需要用计分板,所以播放指令是这样的: ```mcfunction - execute Tg ~ ~ ~ playsound InstID @s ~ ~Ht ~ Vlct Ptc + execute Tg ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc ``` |参数|说明|备注| diff --git a/docs/生成文件的使用说明.md b/docs/生成文件的使用说明.md index 2d814c4..3c8abdf 100644 --- a/docs/生成文件的使用说明.md +++ b/docs/生成文件的使用说明.md @@ -10,7 +10,7 @@ *由于先前的 **读我文件**(README.md) 过于冗杂,现另辟蹊径来给大家全方位的教程。* -*这是本库所生成文件的使用声明,不是使用本库的教程,若要查看**本库的演示程序**使用教程,可点击[此处](%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md);若要查看有关文件结构的内容,可以点击[此处](./%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E.md)* +*这是本库所生成文件的使用声明,不是使用本库的教程,若要查看**本库的大致文档**,可点击[此处](./%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);若要查看有关文件结构的内容,可以点击[此处](./%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E.md)* ## 附加包格式 diff --git a/example_futureFunction.py b/example_futureFunction.py new file mode 100644 index 0000000..cc460df --- /dev/null +++ b/example_futureFunction.py @@ -0,0 +1,13 @@ +import Musicreater.experiment +import Musicreater.plugin +import Musicreater.plugin.mcstructpack + +print( + Musicreater.plugin.mcstructpack.to_mcstructure_addon_in_delay( + Musicreater.experiment.FutureMidiConvertM4.from_midi_file(input("midi路径:"), old_exe_format=False), + Musicreater.plugin.ConvertConfig( + input("输出路径:"), + volume=1 + ), + ) +) diff --git a/setup.py b/setup.py index 183de1a..dd5b90f 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ import Musicreater with open("requirements.txt", "r", encoding="utf-8") as fh: dependences = fh.read().strip().split("\n") -with open("README.md", "r", encoding="utf-8") as fh: +with open("README_EN.md", "r", encoding="utf-8") as fh: long_description = fh.read().replace( "./docs/", "https://github.com/TriM-Organization/Musicreater/blob/master/docs/" )