diff --git a/Musicreater/builtin_plugins/midi_read/__init__.py b/Musicreater/builtin_plugins/midi_read/__init__.py index eaf993b..1d509f5 100644 --- a/Musicreater/builtin_plugins/midi_read/__init__.py +++ b/Musicreater/builtin_plugins/midi_read/__init__.py @@ -19,6 +19,9 @@ Terms & Conditions: License.md in the root directory from .main import MidiImportConfig, MidiImport2MusicPlugin +# constants 里面那些对照表也要导进来写 __all__ 里 +# utils 里面那些拟合曲线也要 + __all__ = [ "MidiImportConfig", "MidiImport2MusicPlugin", diff --git a/Musicreater/builtin_plugins/midi_read/main.py b/Musicreater/builtin_plugins/midi_read/main.py index 063f3a3..1ff6c42 100644 --- a/Musicreater/builtin_plugins/midi_read/main.py +++ b/Musicreater/builtin_plugins/midi_read/main.py @@ -474,9 +474,9 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): if track_properties[0] and ( track_name := midi_track_name_dict.get(track_properties[0]) ): - every_single_track.track_name = track_name + every_single_track.name = track_name if track_properties[2]: - every_single_track.track_instrument = track_properties[2] + every_single_track.instrument = track_properties[2] if track_properties[3]: every_single_track.sound_position.sound_distance = track_properties[3] if track_properties[4]: diff --git a/Musicreater/builtin_plugins/midi_read/utils.py b/Musicreater/builtin_plugins/midi_read/utils.py index 1a37fcd..4430c2a 100644 --- a/Musicreater/builtin_plugins/midi_read/utils.py +++ b/Musicreater/builtin_plugins/midi_read/utils.py @@ -203,7 +203,7 @@ def midi_msgs_to_noteinfo( return ( SingleNote( - midi_pitch=note, + note_pitch=note, note_volume=int((velocity / 127) + 0.5), start_tick=(tk := int(start_time / float(play_speed) / 50000)), keep_tick=round(duration / float(play_speed) / 50000), diff --git a/Musicreater/builtin_plugins/to_commands/main.py b/Musicreater/builtin_plugins/to_commands/main.py index 6583f1b..ac5b249 100644 --- a/Musicreater/builtin_plugins/to_commands/main.py +++ b/Musicreater/builtin_plugins/to_commands/main.py @@ -21,8 +21,9 @@ from dataclasses import dataclass from enum import Enum from pathlib import Path from typing import BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping +from math import ceil -from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos +from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos, MineNote from Musicreater.plugins import ( library_plugin, PluginConfig, @@ -33,9 +34,64 @@ from Musicreater.plugins import ( from Musicreater.exceptions import ZeroSpeedError, IllegalMinimumVolumeError from Musicreater._utils import enumerated_stuffcopy_dictionary +from .progressbar import ProgressBarStyle, DEFAULT_PROGRESSBAR_STYLE, mctick2timestr +from .utils import minenote_to_command_parameters + @dataclass -class CommandConvertionConfig(PluginConfig): ... +class CommandConvertionConfig(PluginConfig): + execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run " + + +@dataclass +class MineCommand: + """存储单个指令的类""" + + command: str + """指令文本""" + conditional: bool = False + """执行是否有条件""" + delay: int = 0 + """执行的延迟""" + annotation: str = "" + """指令注释""" + + def copy(self): + return MineCommand( + command=self.command, + conditional=self.conditional, + delay=self.delay, + annotation=self.annotation, + ) + + @property + def mcfunction_command_string(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, + cmd=self.command, + ) + + def __eq__(self, other) -> bool: + if isinstance(other, self.__class__): + # 不比较注释内容 + return ( + (self.command == other.command) + and (self.conditional == other.conditional) + and (self.delay == other.delay) + ) + else: + return False @library_plugin("notedata_2_command_plugin") @@ -48,3 +104,438 @@ class NoteDataConvert2CommandPlugin(LibraryPluginBase): type=PluginTypes.LIBRARY, license="Same as Musicreater", ) + + @staticmethod + def generate_progressbar( + max_score: int, + scoreboard_name: str, + music_name: str = "", + progressbar_style: ProgressBarStyle = DEFAULT_PROGRESSBAR_STYLE, + execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ", + ) -> List[MineCommand]: + """ + 生成进度条 + + Parameters + ---------- + max_score: int + 最大的积分值 + scoreboard_name: str + 所使用的计分板名称 + progressbar_style: ProgressBarStyle + 此参数详见 ../docs/库的生成与功能文档.md#进度条自定义 + + Returns + ------- + list[MineCommand,] + """ + pgs_style = progressbar_style.base_style + """用于被替换的进度条原始样式""" + + """ + | 标识符 | 指定的可变量 | + |---------|----------------| + | `%%N` | 乐曲名 | + | `%%s` | 当前计分板值 | + | `%^s` | 计分板最大值 | + | `%%t` | 当前播放时间 | + | `%^t` | 曲目总时长 | + | `%%%` | 当前进度比率 | + | `_` | 用以表示进度条占位| + """ + per_value_in_each = max_score / pgs_style.count("_") + """每个进度条代表的分值""" + + result: List[MineCommand] = [] + + if r"%^s" in pgs_style: + pgs_style = pgs_style.replace(r"%^s", str(max_score)) + + if r"%^t" in pgs_style: + pgs_style = pgs_style.replace(r"%^t", mctick2timestr(max_score)) + + sbn_pc = scoreboard_name[:2] + if r"%%%" in pgs_style: + result.append( + MineCommand( + 'scoreboard objectives add {}PercT dummy "百分比计算"'.format( + sbn_pc + ), + annotation="新增临时百分比变量", + ) + ) + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players set MaxScore {} {}".format( + scoreboard_name, max_score + ), + annotation="设定音乐最大延迟分数", + ) + ) + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players set n100 {} 100".format(scoreboard_name), + annotation="设置常量100", + ) + ) + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} = @s {}".format( + sbn_pc + "PercT", scoreboard_name + ), + annotation="赋值临时百分比", + ) + ) + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} *= n100 {}".format( + sbn_pc + "PercT", scoreboard_name + ), + annotation="转换临时百分比之单位至%(扩大精度)", + ) + ) + result.append( + MineCommand( + execute_command_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( + MineCommand( + 'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format( + sbn_pc + ), + annotation="新增临时分变量", + ) + ) + result.append( + MineCommand( + 'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format( + sbn_pc + ), + annotation="新增临时秒变量", + ) + ) + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players set n20 {} 20".format(scoreboard_name), + annotation="设置常量20", + ) + ) + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players set n60 {} 60".format(scoreboard_name), + annotation="设置常量60", + ) + ) + + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} = @s {}".format( + sbn_pc + "TMinT", scoreboard_name + ), + annotation="赋值临时分", + ) + ) + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} /= n20 {}".format( + sbn_pc + "TMinT", scoreboard_name + ), + annotation="转换临时分之单位为秒(缩减精度)", + ) + ) + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} = @s {}".format( + sbn_pc + "TSecT", sbn_pc + "TMinT" + ), + annotation="赋值临时秒", + ) + ) + + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} /= n60 {}".format( + sbn_pc + "TMinT", scoreboard_name + ), + annotation="转换临时分之单位为分(缩减精度)", + ) + ) + + result.append( + MineCommand( + execute_command_head.format( + "@a[scores={" + scoreboard_name + "=1..}]" + ) + + "scoreboard players operation @s {} %= n60 {}".format( + sbn_pc + "TSecT", scoreboard_name + ), + annotation="确定临时秒(框定精度区间)", + ) + ) + + for i in range(pgs_style.count("_")): + npg_stl = ( + pgs_style.replace("_", progressbar_style.played_style, i + 1) + .replace("_", progressbar_style.to_play_style) + .replace(r"%%N", music_name) + .replace( + r"%%s", + '"},{"score":{"name":"*","objective":"' + + scoreboard_name + + '"}},{"text":"', + ) + .replace( + r"%%%", + r'"},{"score":{"name":"*","objective":"' + + sbn_pc + + r'PercT"}},{"text":"%', + ) + .replace( + r"%%t", + r'"},{"score":{"name":"*","objective":"{-}TMinT"}},{"text":":"},' + r'{"score":{"name":"*","objective":"{-}TSecT"}},{"text":"'.replace( + r"{-}", sbn_pc + ), + ) + ) + result.append( + MineCommand( + execute_command_head.format( + r"@a[scores={" + + scoreboard_name + + f"={int(i * per_value_in_each)}..{ceil((i + 1) * per_value_in_each)}" + + r"}]" + ) + + r'titleraw @s actionbar {"rawtext":[{"text":"' + + npg_stl + + r'"}]}', + annotation="进度条显示", + ) + ) + + if r"%%%" in pgs_style: + result.append( + MineCommand( + "scoreboard objectives remove {}PercT".format(sbn_pc), + annotation="移除临时百分比变量", + ) + ) + if r"%%t" in pgs_style: + result.append( + MineCommand( + "scoreboard objectives remove {}TMinT".format(sbn_pc), + annotation="移除临时分变量", + ) + ) + result.append( + MineCommand( + "scoreboard objectives remove {}TSecT".format(sbn_pc), + annotation="移除临时秒变量", + ) + ) + + return result + + @staticmethod + def to_command_list_in_score( + music: SingleMusic, + music_deviation: int = 0, + minimum_volume: float = 0, + scoreboard_name: str = "mscplay", + execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ", + ) -> Tuple[List[List[MineCommand]], int, int]: + """ + 将midi转换为我的世界命令列表 + + Parameters + ---------- + scoreboard_name: str + 我的世界的计分板名称 + + Returns + ------- + tuple( list[list[MineCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 ) + """ + + command_channels: List[List[MineCommand]] = [] + command_amount = 0 + max_score = 0 + + for track in music.music_tracks: + # 如果当前轨道为空 则跳过 + if not track: + continue + + this_channel = [] + + for note in track.minenotes: + max_score = max(max_score, note.start_tick) + + ( + relative_coordinates, + volume_percentage, + mc_pitch, + ) = minenote_to_command_parameters( + note, + pitch_deviation=music_deviation, + ) + + this_channel.append( + MineCommand( + ( + execute_command_head.format( + "@a[scores=({}={})]".format( + scoreboard_name, note.start_tick + ) + .replace("(", r"{") + .replace(")", r"}") + ) + + r"playsound {} @s ^{} ^{} ^{} {} {} {}".format( + track.instrument, + *relative_coordinates, + volume_percentage, + 1.0 if note.percussive else mc_pitch, + minimum_volume, + ) + ), + annotation=( + "在{}播放噪音{}".format( + mctick2timestr(note.start_tick), + track.instrument, + ) + if note.percussive + else "在{}播放乐音{}".format( + mctick2timestr(note.start_tick), + "{}:{:.2f}".format(track.instrument, mc_pitch), + ) + ), + ), + ) + + command_amount += 1 + + if this_channel: + command_channels.append(this_channel) + + return command_channels, command_amount, max_score + + @staticmethod + def to_command_list_in_delay( + music: SingleMusic, + music_deviation: int = 0, + minimum_volume: float = 0, + player_selector: str = "@a", + execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ", + ) -> Tuple[List[MineCommand], int, int]: + """ + 将midi转换为我的世界命令列表,并输出每个音符之后的延迟 + + Parameters + ---------- + player_selector: str + 玩家选择器,默认为`@a` + + Returns + ------- + tuple( list[MineCommand指令,...], int音乐时长游戏刻, int最大同时播放的指令数量 ) + """ + + # 此处 我们把通道视为音轨 + music_command_list = [] + multi = max_multi = 0 + delaytime_previous = 0 + last_note: MineNote + + for note in music.get_minenotes( + start_time=0, + ): + if (tickdelay := (note.start_tick - delaytime_previous)) == 0: + multi += 1 + else: + max_multi = max(max_multi, multi) + multi = 0 + + ( + relative_coordinates, + volume_percentage, + mc_pitch, + ) = minenote_to_command_parameters( + note, + pitch_deviation=music_deviation, + ) + + music_command_list.append( + MineCommand( + command=( + execute_command_head.format(player_selector) + + r"playsound {} @s ^{} ^{} ^{} {} {} {}".format( + note.instrument, + *relative_coordinates, + volume_percentage, + 1.0 if note.percussive else mc_pitch, + minimum_volume, + ) + ), + annotation=( + "在{}播放噪音{}".format( + mctick2timestr(note.start_tick), + note.instrument, + ) + if note.percussive + else "在{}播放乐音{}".format( + mctick2timestr(note.start_tick), + "{}:{:.2f}".format(note.instrument, mc_pitch), + ) + ), + delay=tickdelay, + ), + ) + delaytime_previous = note.start_tick + last_note = note + if music_command_list: + return ( + music_command_list, + last_note.start_tick + last_note.duration_tick, + max_multi + 1, + ) + else: + return [], 0, 0 diff --git a/Musicreater/builtin_plugins/to_commands/progressbar.py b/Musicreater/builtin_plugins/to_commands/progressbar.py new file mode 100644 index 0000000..757a846 --- /dev/null +++ b/Musicreater/builtin_plugins/to_commands/progressbar.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- + +""" +音·创 v3 内置的 指令生成插件的进度条相关内容 +""" + +""" +版权所有 © 2026 金羿、玉衡Alioth +Copyright © 2026 Eilles, YuhengAlioth + +开源相关声明请见 仓库根目录下的 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 BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping + + +# 这个类也有很大的优化空间a +@dataclass(init=False) +class ProgressBarStyle: + """进度条样式类""" + + base_style: str + """基础样式""" + + to_play_style: str + """未播放之样式""" + + played_style: str + """已播放之样式""" + + def __init__( + self, + base_s: Optional[str] = None, + to_play_s: Optional[str] = None, + played_s: Optional[str] = None, + ): + """ + 用于存储进度条样式的类 + + | 标识符 | 指定的可变量 | + |---------|----------------| + | `%%N` | 乐曲名(即传入的文件名)| + | `%%s` | 当前计分板值 | + | `%^s` | 计分板最大值 | + | `%%t` | 当前播放时间 | + | `%^t` | 曲目总时长 | + | `%%%` | 当前进度比率 | + | `_` | 用以表示进度条占位| + + Parameters + ------------ + base_s: str + 基础样式,用以定义进度条整体 + to_play_s: str + 进度条样式:尚未播放的样子 + played_s: str + 已经播放的样子 + + Returns + --------- + ProgressBarStyle 类 + """ + + self.base_style = ( + base_s if base_s else r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]" + ) + self.to_play_style = to_play_s if to_play_s else r"§7=" + self.played_style = played_s if played_s else r"=" + + @classmethod + def from_tuple(cls, tuplized_style: Optional[Tuple[str, Tuple[str, str]]]): + """自旧版进度条元组表示法读入数据(已不建议使用)""" + + if tuplized_style is None: + return cls( + r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]", + r"§7=", + r"=", + ) + + if isinstance(tuplized_style, tuple): + if isinstance(tuplized_style[0], str) and isinstance( + tuplized_style[1], tuple + ): + if isinstance(tuplized_style[1][0], str) and isinstance( + tuplized_style[1][1], str + ): + return cls( + tuplized_style[0], tuplized_style[1][0], tuplized_style[1][1] + ) + raise ValueError( + "元组表示的进度条样式组 {} 格式错误,已不建议使用此功能,请尽快更换。".format( + tuplized_style + ) + ) + + def set_base_style(self, value: str): + """设置基础样式""" + self.base_style = value + + def set_to_play_style(self, value: str): + """设置未播放之样式""" + self.to_play_style = value + + def set_played_style(self, value: str): + """设置已播放之样式""" + self.played_style = value + + def copy(self): + dst = ProgressBarStyle(self.base_style, self.to_play_style, self.played_style) + return dst + + def play_output( + self, + played_ticks: int, + total_ticks: int, + music_name: str = "无题", + ) -> str: + """ + 直接依照此格式输出一个进度条 + + Parameters + ------------ + played_delays: int + 当前播放进度积分值 + total_delays: int + 乐器总延迟数(计分板值) + music_name: str + 曲名 + + Returns + --------- + str + 进度条字符串 + """ + + return ( + self.base_style.replace(r"%%N", music_name) + .replace(r"%%s", str(played_ticks)) + .replace(r"%^s", str(total_ticks)) + .replace(r"%%t", mctick2timestr(played_ticks)) + .replace(r"%^t", mctick2timestr(total_ticks)) + .replace( + r"%%%", + "{:0>5.2f}%".format(int(10000 * played_ticks / total_ticks) / 100), + ) + .replace( + "_", + self.played_style, + (played_ticks * self.base_style.count("_") // total_ticks) + 1, + ) + .replace("_", self.to_play_style) + ) + + +def mctick2timestr(mc_tick: int) -> str: + """ + 将《我的世界》的游戏刻计转为表示时间的字符串 + """ + return "{:0>2d}:{:0>2d}".format(mc_tick // 1200, (mc_tick // 20) % 60) + + +DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle( + r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]", + r"§7=", + r"=", +) +""" +默认的进度条样式 +""" diff --git a/Musicreater/builtin_plugins/to_commands/utils.py b/Musicreater/builtin_plugins/to_commands/utils.py new file mode 100644 index 0000000..fadb242 --- /dev/null +++ b/Musicreater/builtin_plugins/to_commands/utils.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +""" +音·创 v3 内置的指令生成插件的功能方法 +""" + +""" +版权所有 © 2026 金羿、玉衡Alioth +Copyright © 2026 Eilles, YuhengAlioth + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿乐组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + +from typing import ( + BinaryIO, + Optional, + Dict, + List, + Callable, + Tuple, + Mapping, + Union, + Literal, +) + +from Musicreater import MineNote +from Musicreater.constants import MM_INSTRUMENT_DEVIATION_TABLE + + +# 这个函数可以直接被优化成一个只处理音调参数的,没必要完整留着 +def minenote_to_command_parameters( + mine_note: MineNote, + pitch_deviation: float = 0, +) -> Tuple[ + Tuple[float, float, float], + float, + Union[float, Literal[None]], +]: + """ + 将 MineNote 对象转为《我的世界》音符播放所需之参数 + + Parameters + ------------ + mine_note: MineNote + 音符对象 + deviation: float + 音调偏移量 + + Returns + --------- + tuple[float, float, float], float, float + 播放视角坐标, 指令音量参数, 指令音调参数 + """ + + return ( + mine_note.position.position_displacement, + mine_note.volume / 100, + ( + None + if mine_note.percussive + else ( + 2 + ** ( + ( + mine_note.pitch + - 60 + - MM_INSTRUMENT_DEVIATION_TABLE.get(mine_note.instrument, 6) + + pitch_deviation + ) + / 12 + ) + ) + ), + ) diff --git a/Musicreater/data.py b/Musicreater/data.py index e1f9aae..7323de1 100644 --- a/Musicreater/data.py +++ b/Musicreater/data.py @@ -131,7 +131,7 @@ class SoundAtmos: class SingleNote: """存储单个音符的类""" - note_pitch: int + midi_pitch: int """midi音高""" volume: int @@ -151,7 +151,7 @@ class SingleNote: def __init__( self, - midi_pitch: Optional[int], + note_pitch: Optional[int], note_volume: int, start_tick: int, keep_tick: int, @@ -184,7 +184,7 @@ class SingleNote: MineNote 类 """ - self.note_pitch: int = 66 if midi_pitch is None else midi_pitch + self.midi_pitch: int = 66 if note_pitch is None else note_pitch """midi音高""" self.volume: int = note_volume """响度(力度)""" @@ -209,7 +209,7 @@ class SingleNote: try: return cls( - midi_pitch=note_pitch_, + note_pitch=note_pitch_, note_volume=note_volume_, start_tick=start_tick_, keep_tick=duration_, @@ -258,7 +258,7 @@ class SingleNote: return ( ( ( - ((((self.note_pitch << 7) + self.volume) << 17) + self.start_time) + ((((self.midi_pitch << 7) + self.volume) << 17) + self.start_time) << 17 ) + self.duration @@ -294,7 +294,7 @@ class SingleNote: def stringize(self, include_extra_data: bool = False) -> str: return "TrackedNote(Pitch = {}, Volume = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format( - self.note_pitch, + self.midi_pitch, self.volume, self.start_time, self.duration, @@ -310,7 +310,7 @@ class SingleNote: self, ) -> Tuple[int, int, int, int, int]: return ( - self.note_pitch, + self.midi_pitch, self.volume, self.start_time, self.duration, @@ -319,7 +319,7 @@ class SingleNote: def __dict__(self): return { - "Pitch": self.note_pitch, + "Pitch": self.midi_pitch, "Volume": self.volume, "StartTick": self.start_time, "Duration": self.duration, @@ -400,7 +400,7 @@ class MineNote: sound_position.sound_azimuth[1] + adjust_note_updown_panning_degree, ) return cls( - pitch=note.note_pitch + adjust_note_pitch, + pitch=note.midi_pitch + adjust_note_pitch, instrument=note_instrument, volume=note.volume + adjust_note_volume, start_tick=note.start_time, @@ -414,13 +414,13 @@ class MineNote: class SingleTrack(List[SingleNote]): """存储单个轨道的类""" - track_name: str + name: str """轨道之名称""" is_enabled: bool = True """该音轨是否启用""" - track_instrument: str + instrument: str """乐器ID""" is_high_time_precision: bool @@ -441,17 +441,17 @@ class SingleTrack(List[SingleNote]): def __init__( self, *args: SingleNote, - name: str = "未命名音轨", - instrument: str = "", + track_name: str = "未命名音轨", + track_instrument: str = "", precise_time: bool = True, percussion: bool = False, sound_direction: SoundAtmos = SoundAtmos(), extra_information: Dict[str, Any] = {}, ): - self.track_name = name + self.name = track_name """音轨名称""" - self.track_instrument = instrument + self.instrument = track_instrument """乐器ID""" self.is_high_time_precision = precise_time @@ -520,7 +520,7 @@ class SingleTrack(List[SingleNote]): def get_notes( self, start_time: float, end_time: float = inf - ) -> Generator[SingleNote, None, None]: + ) -> Iterator[SingleNote]: """通过开始时间和结束时间来获取音符""" if end_time < start_time: raise ParameterValueError( @@ -528,12 +528,13 @@ class SingleTrack(List[SingleNote]): end_time, start_time ) ) - elif start_time < 0 or end_time < 0: + elif end_time < 0: raise ParameterValueError( - "获取音符的时间范围有误,终止时间`{}`和起始时间`{}`皆不可为负数".format( - end_time, start_time - ) + "获取音符的时间范围有误,终止时间`{}`不可为负数".format(end_time) ) + elif start_time <= 0 and end_time >= self[-1].start_time: + return iter(self) + return ( x for x in self @@ -548,7 +549,7 @@ class SingleTrack(List[SingleNote]): for _note in self.get_notes(range_start_time, range_end_time): yield MineNote.from_single_note( note=_note, - note_instrument=self.track_instrument, + note_instrument=self.instrument, is_persiced_time=self.is_high_time_precision, is_percussive_note=self.is_percussive, sound_position=self.sound_position, @@ -565,10 +566,31 @@ class SingleTrack(List[SingleNote]): return len(self) @property - def track_notes(self) -> List[SingleNote]: + def notes(self) -> List[SingleNote]: """音符列表""" return self + @property + def minenotes(self) -> Iterator[MineNote]: + """ + 直接返回当前音轨所有音符的我的世界数据形式 + """ + return ( + MineNote.from_single_note( + note=_note, + note_instrument=self.instrument, + is_persiced_time=self.is_high_time_precision, + is_percussive_note=self.is_percussive, + sound_position=self.sound_position, + **{ + item.value: argcrv.value_at(_note.start_time) + for item in CurvableParam + if (argcrv := self.argument_curves[item]) + }, + ) + for _note in self + ) + def set_info(self, key: Union[str, Sequence[str]], value: Any): """设置附加信息""" if isinstance(key, str): diff --git a/docs/生成文件的使用说明.md b/docs/生成文件的使用说明.md deleted file mode 100644 index 690d9ac..0000000 --- a/docs/生成文件的使用说明.md +++ /dev/null @@ -1,56 +0,0 @@ -

音·创 Musicreater

- -

- -

- -# 生成文件的使用(正在考虑转移该文档) - -*这是本库所生成文件的使用声明,不是使用本库的教程,若要查看**本库的文档**,可点击此处(暂未推出)* - -## 附加包格式 - -支持的文件后缀:`.MCPACK` - -- 计分板播放器 - - 1. 导入附加包 - 2. 在一个循环方块中输入指令 `function index` - 3. 将需要聆听音乐的实体的播放所用计分板设置为 `1` - 4. 激活循环方块 - 5. 若想要暂停播放,可以停止循环指令方块的激活状态 - 6. 若想要重置某实体的播放,可以将其播放用的计分板重置 - 7. 若要终止全部玩家的播放,在聊天框输入指令 `function stop` - - > 其中 步骤三 和 步骤四 的顺序可以调换。 - -- 延迟播放器 - - 1. 导入附加包 - 2. 在聊天框输入指令 `function index` - 3. 同时激活所生成的循环和脉冲指令方块 - 4. 若要终止播放,在聊天框输入指令 `function stop` 试试看,不确保有用 - - > 需要注意的是,循环指令方块需要一直激活直到音乐结束 - -## 结构格式 - -支持的文件后缀:`.MCSTRUCTURE`、`.BDX` - -1. 将结构导入世界 - -- 延迟播放器 - - 2. 将结构生成的第一个指令方块之模式更改为**脉冲** - 3. 激活脉冲方块 - 4. 若欲重置播放,可以停止对此链的激活,例如停止区块加载 - 5. 此播放器不支持暂停 - -- 计分板播放器 - - 2. 在所生成的第一个指令方块前,放置一个循环指令方块,其朝向应当对着所生成的第一个方块 - 3. 在循环指令方块中输入使播放对象的播放用计分板加分的指令,延迟为 `0`,每次循环增加 `1` 分 - 4. 激活循环方块 - 5. 若想要暂停播放,可以停止循环指令方块的激活状态 - 6. 若想要重置某实体的播放,可以将其播放用的计分板重置 - diff --git a/old-things/Musicreater/experiment.py b/old-things/Musicreater/experiment.py index 2b0e40b..41bade6 100644 --- a/old-things/Musicreater/experiment.py +++ b/old-things/Musicreater/experiment.py @@ -28,10 +28,10 @@ from .old_main import ( mido, ) -from .constants import MIDI_PAN, MIDI_PROGRAM, MIDI_VOLUME +from .old_main import MIDI_PAN, MIDI_PROGRAM, MIDI_VOLUME from .subclass import * from .old_types import ChannelType, FittingFunctionType -from .utils import * +from .old_utils import * class FutureMidiConvertLyricSupport(MidiConvert): @@ -106,7 +106,7 @@ class FutureMidiConvertLyricSupport(MidiConvert): "{}:{:.2f}".format(mc_sound_ID, mc_pitch), ) ), - tick_delay=tickdelay, + delay=tickdelay, ), ) if using_lyric and note.extra_info["LYRIC_TEXT"]: @@ -469,7 +469,7 @@ class FutureMidiConvertKamiRES(MidiConvert): mc_sound_ID, ) ), - tick_delay=tickdelay, + delay=tickdelay, ), ) delaytime_previous = note.start_tick @@ -1006,7 +1006,7 @@ class FutureMidiConvertM4(MidiConvert): "{}:{:.2f}".format(mc_sound_ID, mc_pitch), ) ), - tick_delay=tickdelay, + delay=tickdelay, ), ) delaytime_previous = note.start_tick @@ -1197,7 +1197,7 @@ class FutureMidiConvertM5(MidiConvert): results.append( MineCommand( tracks[all_ticks[i]][j], - tick_delay=( + delay=( ( 0 if ( diff --git a/old-things/Musicreater/old_main.py b/old-things/Musicreater/old_main.py index f8b7aea..32cd852 100644 --- a/old-things/Musicreater/old_main.py +++ b/old-things/Musicreater/old_main.py @@ -37,17 +37,41 @@ from itertools import chain import mido from Musicreater.constants import * -from Musicreater.exceptions import IllegalMinimumVolumeError, NoteBinaryFileVerificationFailed as MusicSequenceVerificationFailed, SingleNoteDecodeError, NoteBinaryFileTypeError as MusicSequenceTypeError, ZeroSpeedError +from Musicreater.exceptions import ( + IllegalMinimumVolumeError, + NoteBinaryFileVerificationFailed as MusicSequenceVerificationFailed, + SingleNoteDecodeError, + NoteBinaryFileTypeError as MusicSequenceTypeError, + ZeroSpeedError, +) -from Musicreater.builtin_plugins.midi_read.constants import MIDI_DEFAULT_PROGRAM_VALUE, MIDI_DEFAULT_VOLUME_VALUE, MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE, MM_TOUCH_PITCHED_INSTRUMENT_TABLE -from Musicreater.builtin_plugins.midi_read.exceptions import NoteOnOffMismatchError, LyricMismatchError -from Musicreater.builtin_plugins.midi_read.utils import volume_2_distance_natural, panning_2_rotation_trigonometric, panning_2_rotation_linear +from Musicreater.builtin_plugins.midi_read.constants import ( + MIDI_DEFAULT_PROGRAM_VALUE, + MIDI_DEFAULT_VOLUME_VALUE, + MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE, + MM_TOUCH_PITCHED_INSTRUMENT_TABLE, +) +from Musicreater.builtin_plugins.midi_read.exceptions import ( + NoteOnOffMismatchError, + LyricMismatchError, +) +from Musicreater.builtin_plugins.midi_read.utils import ( + volume_2_distance_natural, + panning_2_rotation_trigonometric, + panning_2_rotation_linear, +) + +from Musicreater.builtin_plugins.to_commands.progressbar import ( + DEFAULT_PROGRESSBAR_STYLE, + ProgressBarStyle, +) from .old_exceptions import * from .subclass import * from .old_types import * from .old_utils import * + """ 学习笔记: tempo: microseconds per quarter note 毫秒每四分音符,换句话说就是一拍占多少微秒 @@ -980,7 +1004,7 @@ class MusicSequence: # 更新结果信息 midi_channels[msg.channel].append( - that_note := midi_msgs_to_minenote( # 无法强行兼容了,pass + that_note := midi_msgs_to_minenote( # 无法强行兼容了,pass inst_=( msg.note if (_is_percussion := (msg.channel == 9)) @@ -1710,7 +1734,7 @@ class MidiConvert(MusicSequence): "{}:{:.2f}".format(mc_sound_ID, mc_pitch), ) ), - tick_delay=tickdelay, + delay=tickdelay, ), ) delaytime_previous = note.start_tick @@ -1796,7 +1820,7 @@ class MidiConvert(MusicSequence): "{}:{:.2f}".format(mc_sound_ID, mc_pitch), ) ), - tick_delay=tickdelay, + delay=tickdelay, ), ) delaytime_previous[note.sound_name] = note.start_tick diff --git a/old-things/Musicreater/old_plugin/addonpack/main.py b/old-things/Musicreater/old_plugin/addonpack/main.py index 538b582..c7f3185 100644 --- a/old-things/Musicreater/old_plugin/addonpack/main.py +++ b/old-things/Musicreater/old_plugin/addonpack/main.py @@ -99,7 +99,7 @@ def to_addon_pack_in_score( "w", encoding="utf-8", ) as f: - f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]])) + f.write("\n".join([single_cmd.mcfunction_command_string for single_cmd in cmdlist[i]])) index_file.writelines( ( "scoreboard players add @a[scores={" @@ -132,7 +132,7 @@ def to_addon_pack_in_score( f.writelines( "\n".join( [ - single_cmd.cmd + single_cmd.mcfunction_command_string for single_cmd in midi_cvt.form_progress_bar( maxscore, scoreboard_name, progressbar_style ) diff --git a/old-things/Musicreater/old_plugin/bdx.py b/old-things/Musicreater/old_plugin/bdx.py index 7f4a802..68a5ee8 100644 --- a/old-things/Musicreater/old_plugin/bdx.py +++ b/old-things/Musicreater/old_plugin/bdx.py @@ -17,9 +17,8 @@ Terms & Conditions: License.md in the root directory from typing import List -from ..constants import x, y, z from ..subclass import MineCommand -from .common import bottem_side_length_of_smallest_square_bottom_box +from .common import bottem_side_length_of_smallest_square_bottom_box, x, y, z BDX_MOVE_KEY = { "x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"], @@ -161,7 +160,7 @@ def commands_to_BDX_bytes( for command in commands_list: _bytes += form_command_block_in_BDX_bytes( - command.command_text, + command.command, ( (1 if y_forward else 0) if ( @@ -181,7 +180,7 @@ def commands_to_BDX_bytes( condition=command.conditional, needRedstone=False, tickDelay=command.delay, - customName=command.annotation_text, + customName=command.annotation, executeOnFirstTick=False, trackOutput=True, ) diff --git a/old-things/Musicreater/old_plugin/common.py b/old-things/Musicreater/old_plugin/common.py index 7fe050e..ffe27c3 100644 --- a/old-things/Musicreater/old_plugin/common.py +++ b/old-things/Musicreater/old_plugin/common.py @@ -18,6 +18,10 @@ Terms & Conditions: License.md in the root directory import math +x = "x" +y = "y" +z = "z" + def bottem_side_length_of_smallest_square_bottom_box( _total_block_count: int, _max_height: int diff --git a/old-things/Musicreater/old_plugin/mcstructure.py b/old-things/Musicreater/old_plugin/mcstructure.py index 83feeb3..ee7744c 100644 --- a/old-things/Musicreater/old_plugin/mcstructure.py +++ b/old-things/Musicreater/old_plugin/mcstructure.py @@ -20,9 +20,8 @@ from typing import List, Literal, Tuple from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long -from ..constants import x, y, z from ..subclass import MineCommand -from .common import bottem_side_length_of_smallest_square_bottom_box +from .common import bottem_side_length_of_smallest_square_bottom_box, x, y, z def antiaxis(axis: Literal["x", "z", "X", "Z"]): @@ -300,7 +299,7 @@ def commands_to_structure( struct.set_block( coordinate, form_command_block_in_NBT_struct( - command=command.command_text, + command=command.command, coordinate=coordinate, particularValue=( (1 if y_forward else 0) @@ -321,7 +320,7 @@ def commands_to_structure( condition=False, alwaysRun=True, tickDelay=command.delay, - customName=command.annotation_text, + customName=command.annotation, executeOnFirstTick=False, trackOutput=True, compability_version_number=compability_version_, @@ -486,7 +485,7 @@ def commands_to_redstone_delay_structure( struct.set_block( (pos_now[x], 1, pos_now[z]), form_command_block_in_NBT_struct( - command=cmd.command_text, + command=cmd.command, coordinate=(pos_now[x], 1, pos_now[z]), particularValue=command_statevalue(extensioon_direction, forward), # impluse= (0 if first_impluse else 2), @@ -494,7 +493,7 @@ def commands_to_redstone_delay_structure( condition=False, alwaysRun=False, tickDelay=cmd.delay % 2, - customName=cmd.annotation_text, + customName=cmd.annotation, compability_version_number=compability_version_, ), ) @@ -518,7 +517,7 @@ def commands_to_redstone_delay_structure( struct.set_block( (now_pos_copy[x], 1, now_pos_copy[z]), form_command_block_in_NBT_struct( - command=cmd.command_text, + command=cmd.command, coordinate=(now_pos_copy[x], 1, now_pos_copy[z]), particularValue=command_statevalue(extensioon_direction, forward), # impluse= (0 if first_impluse else 2), @@ -526,7 +525,7 @@ def commands_to_redstone_delay_structure( condition=False, alwaysRun=False, tickDelay=cmd.delay % 2, - customName=cmd.annotation_text, + customName=cmd.annotation, compability_version_number=compability_version_, ), ) diff --git a/old-things/Musicreater/old_plugin/websocket/main.py b/old-things/Musicreater/old_plugin/websocket/main.py index 2cd60ab..a337bae 100644 --- a/old-things/Musicreater/old_plugin/websocket/main.py +++ b/old-things/Musicreater/old_plugin/websocket/main.py @@ -98,8 +98,8 @@ def to_websocket_server( "title {} actionbar {}".format( whom_to_play, progressbar_style.play_output( - played_delays=i, - total_delays=musics[music_to_play][1], + played_ticks=i, + total_ticks=musics[music_to_play][1], music_name=music_to_play, ), ), @@ -111,7 +111,7 @@ def to_websocket_server( >= (cmd := musics[music_to_play][0][now_played_cmd]).delay ): await self.send_command( - cmd.command_text.replace(replacement, whom_to_play), + cmd.command.replace(replacement, whom_to_play), callback=self.cmd_feedback, ) now_played_cmd += 1 diff --git a/old-things/Musicreater/old_utils.py b/old-things/Musicreater/old_utils.py index 7bc97b9..e8d2971 100644 --- a/old-things/Musicreater/old_utils.py +++ b/old-things/Musicreater/old_utils.py @@ -83,52 +83,7 @@ def inst_to_sould_with_deviation( ) -def minenote_to_command_parameters( - mine_note: MineNote, - pitch_deviation: float = 0, -) -> Tuple[ - str, - Tuple[float, float, float], - float, - Union[float, Literal[None]], -]: - """ - 将 MineNote 对象转为《我的世界》音符播放所需之参数 - Parameters - ------------ - mine_note: MineNote - 音符对象 - deviation: float - 音调偏移量 - - Returns - --------- - str, tuple[float, float, float], float, float - 我的世界音符ID, 播放视角坐标, 指令音量参数, 指令音调参数 - """ - - return ( - mine_note.sound_name, - mine_note.position_displacement, - mine_note.velocity / 127, - ( - None - if mine_note.percussive - else ( - 2 - ** ( - ( - mine_note.note_pitch - - 60 - - MM_INSTRUMENT_DEVIATION_TABLE.get(mine_note.sound_name, 6) - + pitch_deviation - ) - / 12 - ) - ) - ), - ) def midi_msgs_to_minenote_using_kami_respack( diff --git a/old-things/Musicreater/subclass.py b/old-things/Musicreater/subclass.py index 87a3138..80051d2 100644 --- a/old-things/Musicreater/subclass.py +++ b/old-things/Musicreater/subclass.py @@ -20,7 +20,13 @@ from math import sin, cos, asin, radians, degrees, sqrt, atan from dataclasses import dataclass from typing import Optional, Any, List, Tuple, Union, Dict, Sequence -from .constants import MC_PITCHED_INSTRUMENT_LIST +from Musicreater.constants import MC_PITCHED_INSTRUMENT_LIST +from Musicreater.builtin_plugins.to_commands.main import MineCommand +from Musicreater.builtin_plugins.to_commands.progressbar import ( + ProgressBarStyle, + mctick2timestr, + DEFAULT_PROGRESSBAR_STYLE, +) @dataclass(init=False) @@ -525,80 +531,6 @@ class MineNote: return self.tuplize() == other.tuplize() -@dataclass(init=False) -class MineCommand: - """存储单个指令的类""" - - 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 MineCommand( - 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__() - - @dataclass(init=False) class SingleNoteBox: """存储单个音符盒""" @@ -704,158 +636,3 @@ class SingleNoteBox: if not isinstance(other, self.__class__): return False return self.__str__() == other.__str__() - - -@dataclass(init=False) -class ProgressBarStyle: - """进度条样式类""" - - base_style: str - """基础样式""" - - to_play_style: str - """未播放之样式""" - - played_style: str - """已播放之样式""" - - def __init__( - self, - base_s: Optional[str] = None, - to_play_s: Optional[str] = None, - played_s: Optional[str] = None, - ): - """ - 用于存储进度条样式的类 - - | 标识符 | 指定的可变量 | - |---------|----------------| - | `%%N` | 乐曲名(即传入的文件名)| - | `%%s` | 当前计分板值 | - | `%^s` | 计分板最大值 | - | `%%t` | 当前播放时间 | - | `%^t` | 曲目总时长 | - | `%%%` | 当前进度比率 | - | `_` | 用以表示进度条占位| - - Parameters - ------------ - base_s: str - 基础样式,用以定义进度条整体 - to_play_s: str - 进度条样式:尚未播放的样子 - played_s: str - 已经播放的样子 - - Returns - --------- - ProgressBarStyle 类 - """ - - self.base_style = ( - base_s if base_s else r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]" - ) - self.to_play_style = to_play_s if to_play_s else r"§7=" - self.played_style = played_s if played_s else r"=" - - @classmethod - def from_tuple(cls, tuplized_style: Optional[Tuple[str, Tuple[str, str]]]): - """自旧版进度条元组表示法读入数据(已不建议使用)""" - - if tuplized_style is None: - return cls( - r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]", - r"§7=", - r"=", - ) - - if isinstance(tuplized_style, tuple): - if isinstance(tuplized_style[0], str) and isinstance( - tuplized_style[1], tuple - ): - if isinstance(tuplized_style[1][0], str) and isinstance( - tuplized_style[1][1], str - ): - return cls( - tuplized_style[0], tuplized_style[1][0], tuplized_style[1][1] - ) - raise ValueError( - "元组表示的进度条样式组 {} 格式错误,已不建议使用此功能,请尽快更换。".format( - tuplized_style - ) - ) - - def set_base_style(self, value: str): - """设置基础样式""" - self.base_style = value - - def set_to_play_style(self, value: str): - """设置未播放之样式""" - self.to_play_style = value - - def set_played_style(self, value: str): - """设置已播放之样式""" - self.played_style = value - - def copy(self): - dst = ProgressBarStyle(self.base_style, self.to_play_style, self.played_style) - return dst - - def play_output( - self, - played_delays: int, - total_delays: int, - music_name: str = "无题", - ) -> str: - """ - 直接依照此格式输出一个进度条 - - Parameters - ------------ - played_delays: int - 当前播放进度积分值 - total_delays: int - 乐器总延迟数(计分板值) - music_name: str - 曲名 - - Returns - --------- - str - 进度条字符串 - """ - - return ( - self.base_style.replace(r"%%N", music_name) - .replace(r"%%s", str(played_delays)) - .replace(r"%^s", str(total_delays)) - .replace(r"%%t", mctick2timestr(played_delays)) - .replace(r"%^t", mctick2timestr(total_delays)) - .replace( - r"%%%", - "{:0>5.2f}%".format(int(10000 * played_delays / total_delays) / 100), - ) - .replace( - "_", - self.played_style, - (played_delays * self.base_style.count("_") // total_delays) + 1, - ) - .replace("_", self.to_play_style) - ) - - -def mctick2timestr(mc_tick: int) -> str: - """ - 将《我的世界》的游戏刻计转为表示时间的字符串 - """ - return "{:0>2d}:{:0>2d}".format(mc_tick // 1200, (mc_tick // 20) % 60) - - -DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle( - r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]", - r"§7=", - r"=", -) -""" -默认的进度条样式 -""" diff --git a/old-things/docs/库的生成与功能文档.md b/old-things/docs/库的生成与功能文档.md deleted file mode 100644 index 443a942..0000000 --- a/old-things/docs/库的生成与功能文档.md +++ /dev/null @@ -1,315 +0,0 @@ -

音·创 Musicreater

- -

- -

- -**此为开发相关文档,内容包括:库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释** - -# 库的简单调用 - -参见[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的格式。我们提供了 **音·创** 内置的附加组件,可以借助 `MidiConvert` 对象转换为相应格式。 - - ```python - # 导入 Musicreater - import Musicreater - # 导入附加组件功能 - import Musicreater.plugin - - - # 导入相应的文件格式转换功能 - - # 转换为函数附加包 - import Musicreater.plugin.funpack - # 转换为 BDX 结构文件 - import Musicreater.plugin.bdxfile - # 转换为 mcstructure 结构文件 - import Musicreater.plugin.mcstructfile - # 转换为结构附加包 - import Musicreater.plugin.mcstructpack - # 直接通过 websocket 功能播放(正在开发) - import Musicreater.plugin.websocket - - - # 定义转换参数 - cvt_cfg = Musicreater.plugin.ConvertConfig( - output_path, - volumn, # 音量大小参数 - speed, # 速度倍率 - progressbar, # 进度条样式组(详见下方) - ) - - # 使用附加组件转换,其调用的函数应为: - # Musicreater.plugin.输出格式.播放器格式 - # 值得注意的是,并非所有输出格式都支持所有播放器格式 - # 调用的时候还请注意甄别 - # 例如,以下函数是将 MidiConvert 对象 cvt_mid - # 以 cvt_cfg 指定的参数 - # 以延迟播放器转换为 mcstructure 文件 - Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay( - cvt_mid, - cvt_cfg, - ) - ``` - - -# 生成文件结构 - -## 名词解释 - -|名词|解释|备注| -|--------|-----------|----------| -|指令区|一个用于放置指令系统的区域,通常是常加载区。|常见于服务器指令系统、好友联机房间中| -|指令链(链)|与链式指令方块不同,一个指令链通常指代的是一串由某种非链式指令方块作为开头,后面连着一串链式指令方块的结构。|通常的链都应用于需要“单次激活而多指令”的简单功能| -|起始块|链最初的那个非链式指令方块。|此方块为脉冲方块或重复方块皆可| -|指令系统(系统)|指令系统通常指的是,由一个或多个指令链以及相关红石机构相互配合、一同组成的,为达到某种特定的功能而构建的整体结构。|通常的系统都应用于需要“综合调配指令”的复杂功能。可由多个实现不同功能的模块构成,不同系统之间可以相互调用各自的模块。| -|游戏刻(刻)|游戏的一刻是指《我的世界》的游戏进程循环运行一次所占用的时间。([详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E6%B8%B8%E6%88%8F%E5%88%BB))。指令方块的延迟功能(即指令方块的“延迟刻数”设置项,此项的名称被误译为“已选中项的延迟”)的单位即为`1`游戏刻。|正常情况下,游戏固定以每秒钟 $20$ 刻的速率运行。但是,由于游戏内的绝大多数操作都是基于游戏进程循环而非现实中的时间来计时并进行的,一次游戏循环内也许会发生大量的操作,更多情况下,一秒对应的游戏刻会更少。| -|红石刻|一个红石刻代表了两个游戏刻。([详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E7%BA%A2%E7%9F%B3%E5%88%BB))。红石中继器会带来 $1$~$4$ 个红石刻的延迟,其默认的延迟时间为 $1$ 红石刻。|正常情况下,红石信号在一个红石电路中传输回存在 $\frac{1}{10}$ 秒左右的延迟。但是,同理于游戏刻,一秒对应的红石刻是不定的。| - -## 播放器 - -**音·创**生成的文件可以采用多种方式播放,一类播放方式,我们称其为**播放器**,例如**延迟播放器**和**计分板播放器**等等,以后推出的新的播放器,届时也会在此处更新。 - -为什么要设计这么多播放器?是为了适应不同的播放环境需要。通常情况下,一个音乐中含有多个音符,音符与音符之间存在间隔,这里就产生了不一样的,实现音符间时间间隔的方式。而不同的应用环境下,又会产生不一样的要求。接下来将对不同的播放器进行详细介绍。 - -### 参数释义 - -|参数|说明|备注| -|--------|-----------|----------| -|`ScBd`|指定的计分板名称|| -|`Tg`|播放对象|选择器或玩家名| -|`x`|音发出时对应的分数值|| -|`InstID`|声音效果ID|不同的声音ID可以对应不同的乐器,详见转换[乐器对照表](./%E8%BD%AC%E6%8D%A2%E4%B9%90%E5%99%A8%E5%AF%B9%E7%85%A7%E8%A1%A8.md)| -|`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以 $S$ 表示此参数`Ht`,以Vol表示音量百分比,则计算公式为: $S = \frac{1}{Vol}-1$ | -|`Vlct`|原生我的世界中规定的播放力度|这个参数是一个谜一样的存在,似乎它的值毫不重要……因为无论这个值是多少,我们听起来都差不多。当此音符所在MIDI通道为第一通道,则这个值为 $0.7$ 倍MIDI指定力度,其他则为 $0.9$ 倍。| -|`Ptc`|音符的音高|这是决定音调的参数。以 $P$ 表示此参数, $n$ 表示其在MIDI中的编号, $x$ 表示一定的音调偏移,则计算公式为: $P = 2^\frac{n-60-x}{12}$。之所以存在音调偏移是因为在《我的世界》中,不同的[乐器存在不同的音域](https://zh.minecraft.wiki/wiki/%E9%9F%B3%E7%AC%A6%E7%9B%92#%E4%B9%90%E5%99%A8),我们通过音调偏移来进行调整。| - -### 播放器内容 - -1. 计分板播放器 - - 计分板播放器是一种传统的《我的世界》音乐播放方式。通过对于计分板加分来实现播放不同的音符。一个很简单的原理,就是**用不同的计分板分值对应不同的音符**,再通过加分,来达到那个分值,即播放出来。 - - 在**音·创**中,用来达到这种效果的指令是这样的: - - ```mcfunction - execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc - ``` - - - - 后四个参数决定了这个音的性质,而前两个参数仅仅是为了决定音播放的时间。 - -2. 延迟播放器 - - 延迟播放器是通过《我的世界》游戏中,指令方块的设置项“延迟刻数”来达到定位音符的效果。**将所有的音符依照其播放时距离乐曲开始时的时间(毫秒),放在一个序列内,再计算音符两两之间对应的时间差值,转换为《我的世界》内对应的游戏刻数之后填入指令方块的设置中。** - - 在**音·创**中,由于此方式播放的音乐不需要用计分板,所以播放指令是这样的: - - ```mcfunction - execute Tg ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc - ``` - - - 其中后四个参数决定了这个音的性质。 - - 由于这样的延迟数据是依赖于指令方块的设置项,所以使用这种播放器所转换出的结果仅可以存储在包含方块NBT信息及方块实体NBT信息的结构文件中,或者直接输出至世界。 - -3. 中继器播放器 - - 中继器播放器是一种传统的《我的世界》红石音乐播放方式,利用游戏内“红石组件”之“红石中继器”以达到定位音符之用。**但是,中继器的延迟为1红石刻** - - -## 文件格式 - -1. 附加包格式(`.mcpack`) - - 使用附加包格式导出音乐,若采用计分板 播放器,则音乐会以指令函数文件(`.mcfunction`)存储于附加包内。而若为延迟或中继器播放器,则音乐回以结构文件(`.mcstructure`)存储。在所生成的附加包中,函数文件的存储结构应为: - - - `functions\` - - `index.mcfunction` - - `stop.mcfunction` - - `mscply\` - - `progressShow.mcfunction` - - `track1.mcfunction` - - `track2.mcfunction` - - ... - - `trackN.mcfunction` - - `structures\` - - `XXX_main.mcstructure` - - `XXX_start.mcstructure` - - `XXX_reset.mcstructure` - - `XXX_pgb.mcstructure` - - 如图,其中,`index.mcfunction`文件、`stop.mcfunction`文件和`mscply`文件夹存在于函数目录的根下;在`mscply`目录中,包含音乐导出的众多音轨播放文件(`trackX.mcfunction`)。同时,若使用计分板播放器生成此包时启用生成进度条,则会包含`progressShow.mcfunction`文件。若选择延迟或中继器播放器,则会生成`structures`目录以及相关`.mcstructure`文件,其中`mian`表示音乐播放用的主要结构;`start`是用于初始化播放的部分,仅包含一个指令方块即起始块;`reset`和`pgb`仅在启用生成进度条时出现,前者用于重置临时计分板,后者用于显示进度条。 - - `index.mcfunction`用于开始播放: - - 1. 若为计分板播放器,则其中包含打开各个音轨对应函数的指令,以及加分指令,这里的加分,是将**播放计分板的值大于等于 $1$ 的所有玩家**的播放计分板分数增加 $1$。同时,若生成此包时选择了自动重置计分板的选项,则会包含一条重置计分板的指令。 - - 2. 若为延迟或中继器播放器,则其中的指令仅包含用以正确加载结构的`structure`指令。 - - `stop.mcfunction`用于终止播放: - - 1. 若为计分板播放器,则其中包含将**全体玩家的播放计分板**重置的指令。 - - 2. 若为延迟或中继器播放器,则其中包含**停用命令方块**和**启用命令方块**的指令。~~然鹅实际上对于播放而言是一点用也没有~~ - - > 你知道吗?音·创的最早期版本“《我的世界》函数音乐生成器”正是用函数来播放,不过这个版本采取的读入数据的形式大有不同。 - -2. 生成结构的方式 - - 无论是音·创生成的是何种结构,`MCSTRUCTURE`还是`BDX`,都会依照此处的格式来生成。此处我们想说明的结构的格式不是结构文件存储的格式,而是结构导出之后方块摆放的方式。结构文件存储的格式这一点,在各个《我的世界》开发的相关网站上都可能会有说明。 - - 考虑到进行《我的世界》游戏开发时,为了节约常加载区域,很多游戏会将指令区设立为一种层叠式的结构。这种结构会限制每一层的指令系统的高度,但是虽然长宽也是有限的,却仍然比其纵轴延伸得更加自由。 - - 所以,结构的生成形状依照给定的高度和内含指令的数量决定。其 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整。用数学公式的方式表达,则是: - - $$ MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor $$ - - 其中,$MaxZ$ 即生成结构的$Z$轴最大延伸长度,$NoC$ 表示链结构中所含指令方块的个数,$MaxH$ 表示给定的生成结构的最大高度。 - - 我们的结构生成器在生成指令链时,将首先以相对坐标系 $(0, 0, 0)$ (即相对原点)开始,自下向上堆叠高度轴(即 $Y$ 轴)的长,当高度轴达到了限制的高度时,便将 $Z$ 轴向正方向堆叠 $1$ 个方块,并开始自上向下重新堆叠,直至高度轴坐标达到相对为 $0$。若当所生成结构的 $Z$ 轴长达到了其最大延伸长度,则此结构生成器将反转 $Z$ 轴的堆叠方向,直至 $Z$ 轴坐标相对为 $0$。如此往复,直至指令链堆叠完成。 - - -# 进度条自定义 - -因为我们提供了可以自动转换进度条的功能,因此在这里给出进度条自定义参数的详细解释。 - -一个进度条,明显地,有**固定部分**和**可变部分**来构成。而可变部分又包括了文字和图形两种(当然,《我的世界》里头的进度条,可变的图形也就是那个“条”了)。这一点你需要了解,因为后文中包含了很多这方面的概念需要你了解。 - -进度条的自定义功能使用一个字符串来定义自己的样式,其中包含众多**标识符**来表示可变部分。 - -标识符如下(注意大小写): - -| 标识符 | 指定的可变量 | -|---------|----------------| -| `%%N` | 乐曲名(即传入的文件名)| -| `%%s` | 当前计分板值 | -| `%^s` | 计分板最大值 | -| `%%t` | 当前播放时间 | -| `%^t` | 曲目总时长 | -| `%%%` | 当前进度比率 | -| `_` | 用以表示进度条占位| - -表示进度条占位的 `_` 是用来标识你的进度条的。也就是可变部分的唯一的图形部分。 - -**样式定义字符串(基础样式)**的样例如下,这也是默认进度条的基础样式: - -```▶ %%N [ %%s/%^s %%% __________ %%t|%^t]``` - -这是单独一行的进度条,当然你也可以制作多行的,如果是一行的,输出时所使用的指令便是 `title`,而如果是多行的话,输出就会用 `titleraw` 作为进度条字幕。 - -哦对了,上面的只不过是样式定义,同时还需要定义的是可变图形的部分,也就是进度条上那个真正的“条”。 - -对于这个我们就采用了固定参数的方法,对于一个进度条,无非就是“已经播放过的”和“没播放过的”两种形态,例如,我们默认的进度“条”(**可变样式**)的定义是这样的: - -**可变样式甲(已播放样式)**:`'§e=§r'` - -**可变样式乙(未播放样式)**:`'§7=§r'` - -综合起来,把这些参数传给函数需要一个参数整合,使用位于 `Musicreater/subclass.py` 下的 `ProgressBarStyle` 类进行定义: - -我们的默认定义参数如下: - -```python -DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle( - r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]", - r"§e=§r", - r"§7=§r", -) -``` - -*为了避免生成错误,请尽量避免使用标识符作为定义样式字符串的其他部分* - diff --git a/old-things/let_future_java.py b/old-things/let_future_java.py index 9fb519c..e494581 100644 --- a/old-things/let_future_java.py +++ b/old-things/let_future_java.py @@ -71,7 +71,7 @@ def to_zip_pack_in_score( "w", encoding="utf-8", ) as f: - f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]])) + f.write("\n".join([single_cmd.mcfunction_command_string for single_cmd in cmdlist[i]])) index_file.writelines( ( "scoreboard players add @a[score_{0}_min=1] {0} 1\n".format( @@ -97,7 +97,7 @@ def to_zip_pack_in_score( f.writelines( "\n".join( [ - single_cmd.cmd + single_cmd.mcfunction_command_string for single_cmd in midi_cvt.form_java_progress_bar( maxscore, scoreboard_name, progressbar_style )