From ba7b10a25f142da6e3fa6c82ad0d9759a3143410 Mon Sep 17 00:00:00 2001 From: Eilles Date: Fri, 13 Mar 2026 22:56:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=95=B4=E4=BD=BF=E7=94=A8=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=B7=B2=E7=BB=8F=E6=B5=8B=E8=AF=95=E4=B8=80=E9=81=8D?= =?UTF-8?q?=E4=BA=86=EF=BC=8C=E5=AE=8C=E6=95=B4=E6=B5=81=E7=A8=8B=E6=98=AF?= =?UTF-8?q?=E6=B2=A1=E9=97=AE=E9=A2=98=E7=9A=84=EF=BC=8C=E6=8E=A5=E4=B8=8B?= =?UTF-8?q?=E6=9D=A5=E5=B0=B1=E6=98=AF=E6=AF=8F=E4=B8=AA=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E7=9A=84=E9=82=A3=E4=BA=9B=E5=B0=8F=E5=8A=9F=E8=83=BD=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E4=BC=9A=E6=9C=89=E4=B8=80=E4=BA=9B=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=88=B0=E7=9A=84=E5=9C=B0=E6=96=B9=E3=80=82?= =?UTF-8?q?=E8=BF=99=E4=BA=9B=E5=8A=9F=E8=83=BD=E8=99=BD=E7=84=B6=E7=BB=86?= =?UTF-8?q?=E6=9E=9D=E6=9C=AB=E8=8A=82=EF=BC=8C=E4=BD=86=E4=B9=9F=E9=83=BD?= =?UTF-8?q?=E4=B8=BE=E8=B6=B3=E8=BD=BB=E9=87=8D=EF=BC=8C=E5=BA=94=E5=BD=93?= =?UTF-8?q?=E5=9C=A8=E5=BC=80=E5=8F=91=E5=87=BA=E4=BA=86=E4=BC=B6=E4=BC=A6?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E7=AB=99=E7=9A=84=E6=97=B6=E5=80=99=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E3=80=82=E6=89=80=E4=BB=A5=E7=9B=AE=E5=89=8D=E7=9A=84?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E9=87=8D=E5=BF=83=E8=BD=AC=E7=A7=BB=E5=88=B0?= =?UTF-8?q?=E4=BC=B6=E4=BC=A6=E5=B7=A5=E4=BD=9C=E7=AB=99=E4=B8=8A=EF=BC=8C?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8F=92=E4=BB=B6=E4=BB=8E=20v2=20=E5=88=B0?= =?UTF-8?q?=20v3=20=E7=9A=84=E7=A7=BB=E6=A4=8D=EF=BC=8C=E4=BA=A4=E7=94=B1?= =?UTF-8?q?=E5=85=B6=E4=BB=96=E4=BA=BA=E6=9D=A5=E5=A4=84=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commands_to_structure/__init__.py | 8 + .../commands_to_structure/bdx.py | 7 +- .../commands_to_structure/common.py | 4 +- .../commands_to_structure/main.py | 148 ++++++++++++++++++ .../commands_to_structure/mcstructure.py | 26 ++- Musicreater/builtin_plugins/midi_read/main.py | 75 +++++---- .../builtin_plugins/midi_read/utils.py | 2 +- .../builtin_plugins/to_commands/main.py | 14 +- .../builtin_plugins/to_commands/utils.py | 4 +- Musicreater/data.py | 15 +- Musicreater/main.py | 17 +- Musicreater/plugins.py | 1 + docs/编写插件.md | 1 + examples/exp_dataexport_plugin.py | 1 + examples/exp_importdata_plugin.py | 1 + .../old_plugin/mcstructfile/main.py | 2 +- test_convert_midi.py | 34 ++++ test_read.py | 6 +- 18 files changed, 296 insertions(+), 70 deletions(-) create mode 100644 test_convert_midi.py diff --git a/Musicreater/builtin_plugins/commands_to_structure/__init__.py b/Musicreater/builtin_plugins/commands_to_structure/__init__.py index 82a6b6c..fcf2eb7 100644 --- a/Musicreater/builtin_plugins/commands_to_structure/__init__.py +++ b/Musicreater/builtin_plugins/commands_to_structure/__init__.py @@ -15,3 +15,11 @@ Terms & Conditions: License.md in the root directory # 睿乐组织 开发交流群 861684859 # Email TriM-Organization@hotmail.com # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + +from .main import McstructureExportConfig, NoteDataConvert2CommandPlugin + + +__all__ = [ + "McstructureExportConfig", + "NoteDataConvert2CommandPlugin", +] \ No newline at end of file diff --git a/Musicreater/builtin_plugins/commands_to_structure/bdx.py b/Musicreater/builtin_plugins/commands_to_structure/bdx.py index 728f79b..819f0a3 100644 --- a/Musicreater/builtin_plugins/commands_to_structure/bdx.py +++ b/Musicreater/builtin_plugins/commands_to_structure/bdx.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- """ -存放有关BDX结构操作的内容 +音·创 v3 内置的 Minecraft 结构生成插件中有关 BDX 结构操作的内容 """ """ -版权所有 © 2025 金羿 & 诸葛亮与八卦阵 -Copyright © 2025 Eilles & bgArray +版权所有 © 2026 金羿、玉衡Alioth +Copyright © 2026 Eilles, YuhengAlioth 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory @@ -15,6 +15,7 @@ Terms & Conditions: License.md in the root directory # Email TriM-Organization@hotmail.com # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + from typing import List from Musicreater.builtin_plugins.to_commands import MineCommand diff --git a/Musicreater/builtin_plugins/commands_to_structure/common.py b/Musicreater/builtin_plugins/commands_to_structure/common.py index ffe27c3..5cb2344 100644 --- a/Musicreater/builtin_plugins/commands_to_structure/common.py +++ b/Musicreater/builtin_plugins/commands_to_structure/common.py @@ -4,8 +4,8 @@ """ """ -版权所有 © 2025 金羿 & 诸葛亮与八卦阵 -Copyright © 2025 Eilles & bgArray +版权所有 © 2026 金羿、玉衡Alioth +Copyright © 2026 Eilles, YuhengAlioth 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory diff --git a/Musicreater/builtin_plugins/commands_to_structure/main.py b/Musicreater/builtin_plugins/commands_to_structure/main.py index e69de29..b791462 100644 --- a/Musicreater/builtin_plugins/commands_to_structure/main.py +++ b/Musicreater/builtin_plugins/commands_to_structure/main.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- + +""" +音·创 v3 内置的 Minecraft 结构生成插件 +""" + +""" +版权所有 © 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 io import BytesIO +from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple +from pathlib import Path +from dataclasses import dataclass + +from TrimMCStruct import Structure + +from Musicreater import SingleMusic, SingleTrack +from Musicreater.plugins import ( + PluginConfig, + PluginMetaInformation, + PluginTypes, + music_output_plugin, + MusicOutputPluginBase, + track_output_plugin, + TrackOutputPluginBase, +) + +from Musicreater.builtin_plugins.to_commands import ( + MineCommand, + NoteDataConvert2CommandPlugin, +) + +from .mcstructure import ( + COMPABILITY_VERSION_117, + COMPABILITY_VERSION_119, + commands_to_structure, + commands_to_redstone_delay_structure, +) + + +@dataclass +class McstructureExportConfig(PluginConfig): + """ + 导出 MCSTRUCTURE 结构插件的配置类 + """ + + music_deviation: float = 0 + """ + 全曲音调偏移调整,单位为 Midi Pitch + """ + minimum_volume: float = 0.01 + """ + 指令参数:最小音量 + """ + player_selector: str = "@a" + """ + 玩家选择器 + """ + max_height: int = 64 + """ + 生成结构的最大高度 + """ + enable_old_execute_format: bool = False + """ + 是否使用旧版指令格式 + """ + + @property + def execute_command_head(self) -> str: + return ( + "execute {} ~ ~ ~ " + if self.enable_old_execute_format + else "execute as {} at @s positioned ~ ~ ~ run " + ) + + +@music_output_plugin("music_to_mcstructure_in_delay_plugin") +class MusicExportToMcstructureWithDelayPlayerPlugin(MusicOutputPluginBase): + + metainfo = PluginMetaInformation( + name="导出全曲结构插件(mcstructure结构、延迟播放器)", + author="金羿、玉衡Alioth", + description="将音·创 v3 的整首音乐数据,以指令方块延迟的播放形式,导出为基岩版 MCSTRUCTURE 结构", + version=(0, 0, 1), + type=PluginTypes.FUNCTION_MUSIC_EXPORT, + license="Same as Musicreater", + dependencies=("notedata_to_command_plugin",), + ) + + supported_formats = ("MCSTRUCTURE",) + + @staticmethod + def _go_convertion( + data: SingleMusic, config: McstructureExportConfig + ) -> Tuple[Structure, Tuple[int, int, int], int]: + + command_list, max_delay = ( + NoteDataConvert2CommandPlugin.to_command_list_in_delay( + music=data, + music_deviation=config.music_deviation, + minimum_volume=config.minimum_volume, + player_selector=config.player_selector, + execute_command_head=config.execute_command_head, + )[:2] + ) + + struct, size, end_pos = commands_to_structure( + command_list, + config.max_height - 1, + compability_version_=( + COMPABILITY_VERSION_117 + if config.enable_old_execute_format + else COMPABILITY_VERSION_119 + ), + ) + + return struct, size, max_delay + + def dump(self, data: SingleMusic, file_path: Path, config: McstructureExportConfig): + + struct, size, max_delay = self._go_convertion(data, config) + + file_path.parent.mkdir(parents=True, exist_ok=True) + + with file_path.open("wb") as f: + struct.dump(f) + + return size, max_delay + + def stream_dump( + self, data: SingleMusic, config: McstructureExportConfig + ) -> Iterator[bytes]: + struct, size, max_delay = self._go_convertion(data, config) + + b_out = BytesIO() + struct.dump(b_out) + b_out.seek(0) + + yield from b_out diff --git a/Musicreater/builtin_plugins/commands_to_structure/mcstructure.py b/Musicreater/builtin_plugins/commands_to_structure/mcstructure.py index c6e8c72..a2d50c1 100644 --- a/Musicreater/builtin_plugins/commands_to_structure/mcstructure.py +++ b/Musicreater/builtin_plugins/commands_to_structure/mcstructure.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- """ -存放有关MCSTRUCTURE结构操作的内容 +音·创 v3 内置的 Minecraft 结构生成插件中有关 MCSTRUCTURE 结构操作的内容 """ """ -版权所有 © 2025 金羿 & 诸葛亮与八卦阵 -Copyright © 2025 Eilles & bgArray +版权所有 © 2026 金羿、玉衡Alioth +Copyright © 2026 Eilles, YuhengAlioth 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory @@ -25,10 +25,17 @@ from .common import bottem_side_length_of_smallest_square_bottom_box, x, y, z def antiaxis(axis: Literal["x", "z", "X", "Z"]): + """ + 在 x-z 平面上,返回指定轴的另一个轴 + """ + return z if axis == x else x def forward_IER(forward: bool): + """ + 把用逻辑值标记的方向值缓存正负数,用以乘上增量 + """ return 1 if forward else -1 @@ -46,6 +53,9 @@ AXIS_PARTICULAR_VALUE = { False: 2, }, } +""" +指令方块朝向对应特殊值 +""" # 1.19的结构兼容版本号 COMPABILITY_VERSION_119: int = 17959425 @@ -278,12 +288,12 @@ def commands_to_structure( 结构类, 结构占用大小, 终点坐标 """ - _sideLength = bottem_side_length_of_smallest_square_bottom_box( + _side_length = bottem_side_length_of_smallest_square_bottom_box( len(commands), max_height ) struct = Structure( - size=(_sideLength, max_height, _sideLength), # 声明结构大小 + size=(_side_length, max_height, _side_length), # 声明结构大小 compability_version=compability_version_, ) @@ -311,7 +321,7 @@ def commands_to_structure( (3 if z_forward else 2) if ( ((now_z != 0) and (not z_forward)) - or (z_forward and (now_z != _sideLength - 1)) + or (z_forward and (now_z != _side_length - 1)) ) else 5 ) @@ -336,7 +346,7 @@ def commands_to_structure( now_z += 1 if z_forward else -1 - if ((now_z >= _sideLength) and z_forward) or ( + if ((now_z >= _side_length) and z_forward) or ( (now_z < 0) and (not z_forward) ): now_z -= 1 if z_forward else -1 @@ -348,7 +358,7 @@ def commands_to_structure( ( now_x + 1, max_height if now_x or now_z else now_y, - _sideLength if now_x else now_z, + _side_length if now_x else now_z, ), (now_x, now_y, now_z), ) diff --git a/Musicreater/builtin_plugins/midi_read/main.py b/Musicreater/builtin_plugins/midi_read/main.py index 0c20523..2fc44c6 100644 --- a/Musicreater/builtin_plugins/midi_read/main.py +++ b/Musicreater/builtin_plugins/midi_read/main.py @@ -125,7 +125,7 @@ class TrackDivisionDict( ): """ 音轨分轨字典 - 键为音轨信息元组[音轨编号, 通道编号, 音符名称, 音量, 声相] + 键为音轨信息元组[音轨编号, 通道编号, 乐器名称, 音量, 声相] 值为音轨对象 """ @@ -172,7 +172,7 @@ class TrackDivisionDict( return self[key] -@music_input_plugin("midi_2_music_plugin") +@music_input_plugin("midi_to_music_plugin") class MidiImport2MusicPlugin(MusicInputPluginBase): """Midi 音乐数据导入插件""" @@ -193,7 +193,7 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): config: Optional[MidiImportConfig] = MidiImportConfig(), ) -> SingleMusic: return self.midifile_2_singlemusic( - mido.MidiFile(file=bytes_buffer_in), + mido.MidiFile(file=bytes_buffer_in, clip=True), config if config else MidiImportConfig(), ) @@ -202,7 +202,8 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): ) -> SingleMusic: """从 Midi 文件导入音乐数据""" return self.midifile_2_singlemusic( - mido.MidiFile(filename=file_path), config if config else MidiImportConfig() + mido.MidiFile(filename=file_path, clip=True), + config if config else MidiImportConfig(), ) @staticmethod @@ -264,17 +265,15 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): """音符计数""" note_count_per_instrument: Dict[str, int] = {} """乐器使用统计""" - microseconds = 0 - """当前的微妙时间""" note_queue_A: Dict[int, List[Tuple[int, int]]] = ( enumerated_stuffcopy_dictionary(staff=[]) ) - """音符队列甲 Dict[通道, List[Tuple[int音高, int乐器, int轨道]]]""" - note_queue_B: Dict[int, List[Tuple[int, int, int]]] = ( + """音符队列甲 Dict[通道, List[Tuple[int音高, int轨道]]]""" + note_queue_B: Dict[int, List[Tuple[int, int, int, int, int]]] = ( enumerated_stuffcopy_dictionary(staff=[]) ) - """音符队列乙 Dict[通道, List[Tuple[int音高, int微秒时间]]]""" + """音符队列乙 Dict[通道, List[Tuple[int力度, int乐器, int音量, int偏移, int微秒时间]]]""" midi_lyric_cache: List[Tuple[int, str]] = [] """歌词缓存 List[Tuple[int微秒时间, str歌词内容]]""" @@ -287,8 +286,14 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): """轨道名称字典 Dict[int轨道编号, str轨道名称]""" for track_no, message_track in enumerate(midi.tracks): + # 每个音轨单独重置 + + microseconds = 0 + """当前的微妙时间""" for msg in message_track: if msg.type == "set_tempo": + # Tempo 改变是一个全局的控制 + # 而且应该是很早出现的一个 Midi 消息 midi_tempo = msg.tempo if msg.time != 0: @@ -304,6 +309,7 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): elif msg.is_cc(7): # Control Change 更改当前通道的 音量 的事件(大幅度,最高有效位) + # print("通道、轨道、音量修改:",msg.channel, track_no, msg.value) value_controler_per_channel[msg.channel][ ControlerKeys.MIDI_VOLUME ] = msg.value @@ -333,13 +339,19 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): # (音高, 轨道) note_queue_A[msg.channel].append((msg.note, track_no)) # 音符队列乙(按通道分隔) - # (乐器, 力度, 微秒) + # (力度, 乐器, 音量, 偏移, 微秒) note_queue_B[msg.channel].append( ( + msg.velocity, value_controler_per_channel[msg.channel][ ControlerKeys.MIDI_PROGRAM ], - msg.velocity, + value_controler_per_channel[msg.channel][ + ControlerKeys.MIDI_VOLUME + ], + value_controler_per_channel[msg.channel][ + ControlerKeys.MIDI_PAN + ], microseconds, ) ) @@ -351,26 +363,28 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): if ( msg.note, - value_controler_per_channel[msg.channel][ - ControlerKeys.MIDI_PROGRAM - ], + track_no, ) in note_queue_A[msg.channel]: # 在甲队列中发现了同一个 音高和乐器且在同轨道 的音符 # 获取其音符力度和微秒数 - _velocity, _program, _ms = note_queue_B[msg.channel][ - note_queue_A[msg.channel].index((msg.note, track_no)) - ] + _velocity, _program, _volume, _panning, _start_ms = ( + note_queue_B[msg.channel][ + note_queue_A[msg.channel].index((msg.note, track_no)) + ] + ) # 在队列中删除此音符 note_queue_A[msg.channel].remove((msg.note, track_no)) - note_queue_B[msg.channel].remove((_velocity, _program, _ms)) + note_queue_B[msg.channel].remove( + (_velocity, _program, _volume, _panning, _start_ms) + ) _lyric = "" # 找一找歌词吧 if midi_lyric_cache: for i in range(len(midi_lyric_cache)): - if midi_lyric_cache[i][0] >= _ms: + if midi_lyric_cache[i][0] >= _start_ms: _lyric = midi_lyric_cache.pop(i)[1] break @@ -385,15 +399,11 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): ), note=(_program if _is_percussion else msg.note), percussive=_is_percussion, - volume=value_controler_per_channel[msg.channel][ - ControlerKeys.MIDI_VOLUME - ], + volume=_volume, velocity=_velocity, - panning=value_controler_per_channel[msg.channel][ - ControlerKeys.MIDI_PAN - ], - start_time=_ms, # 微秒 - duration=microseconds - _ms, # 微秒 + panning=_panning, + start_time=_start_ms, # 微秒 + duration=microseconds - _start_ms, # 微秒 play_speed=config.speed_multiplier, midi_reference_table=( config.percussion_note_reference_table @@ -407,6 +417,8 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): ) ) + # print(that_note.start_time, end=", ") + divided_tracks[ ( track_no, @@ -465,15 +477,16 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): }, ) for track_properties, every_single_track in divided_tracks.items(): + # [音轨编号, 通道编号, 乐器名称, 音量, 声相] if track_properties[0] and ( track_name := midi_track_name_dict.get(track_properties[0]) - ): + ): # 音轨编号 every_single_track.name = track_name - if track_properties[2]: + if track_properties[2]: # 乐器名称 every_single_track.instrument = track_properties[2] - if track_properties[3]: + if track_properties[3]: # 音量 every_single_track.sound_position.sound_distance = track_properties[3] - if track_properties[4]: + if track_properties[4]: # 声相 every_single_track.sound_position.sound_azimuth = track_properties[4] final_music.append(every_single_track) diff --git a/Musicreater/builtin_plugins/midi_read/utils.py b/Musicreater/builtin_plugins/midi_read/utils.py index 4430c2a..af45607 100644 --- a/Musicreater/builtin_plugins/midi_read/utils.py +++ b/Musicreater/builtin_plugins/midi_read/utils.py @@ -204,7 +204,7 @@ def midi_msgs_to_noteinfo( return ( SingleNote( note_pitch=note, - note_volume=int((velocity / 127) + 0.5), + note_volume=velocity, start_tick=(tk := int(start_time / float(play_speed) / 50000)), keep_tick=round(duration / float(play_speed) / 50000), mass_precision_time=round( diff --git a/Musicreater/builtin_plugins/to_commands/main.py b/Musicreater/builtin_plugins/to_commands/main.py index fc0ad42..b0776b2 100644 --- a/Musicreater/builtin_plugins/to_commands/main.py +++ b/Musicreater/builtin_plugins/to_commands/main.py @@ -94,7 +94,7 @@ class MineCommand: return False -@library_plugin("notedata_2_command_plugin") +@library_plugin("notedata_to_command_plugin") class NoteDataConvert2CommandPlugin(LibraryPluginBase): metainfo = PluginMetaInformation( name="音符数据指令支持插件", @@ -481,8 +481,8 @@ class NoteDataConvert2CommandPlugin(LibraryPluginBase): @staticmethod def to_command_list_in_score( music: SingleMusic, - music_deviation: int = 0, - minimum_volume: float = 0, + music_deviation: float = 0, + minimum_volume: float = 0.01, scoreboard_name: str = "mscplay", execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ", ) -> Tuple[List[List[MineCommand]], int, int]: @@ -565,8 +565,8 @@ class NoteDataConvert2CommandPlugin(LibraryPluginBase): @staticmethod def to_command_list_in_delay( music: SingleMusic, - music_deviation: int = 0, - minimum_volume: float = 0, + music_deviation: float = 0, + minimum_volume: float = 0.01, player_selector: str = "@a", execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ", ) -> Tuple[List[MineCommand], int, int]: @@ -583,7 +583,7 @@ class NoteDataConvert2CommandPlugin(LibraryPluginBase): tuple( list[MineCommand指令,...], int音乐时长游戏刻, int最大同时播放的指令数量 ) """ - # 此处 我们把通道视为音轨 + # 音轨判断 music_command_list = [] multi = max_multi = 0 delaytime_previous = 0 @@ -611,7 +611,7 @@ class NoteDataConvert2CommandPlugin(LibraryPluginBase): MineCommand( command=( execute_command_head.format(player_selector) - + r"playsound {} @s ^{} ^{} ^{} {} {} {}".format( + + "playsound {} @s ^{} ^{} ^{} {} {} {}".format( note.instrument, *relative_coordinates, volume_percentage, diff --git a/Musicreater/builtin_plugins/to_commands/utils.py b/Musicreater/builtin_plugins/to_commands/utils.py index 88e4ed9..a6c563e 100644 --- a/Musicreater/builtin_plugins/to_commands/utils.py +++ b/Musicreater/builtin_plugins/to_commands/utils.py @@ -48,7 +48,7 @@ def minenote_to_command_parameters( ---- mine_note: MineNote 我的世界音符对象 - deviation: float + pitch_deviation: float 音调偏移量 返回 @@ -59,7 +59,7 @@ def minenote_to_command_parameters( return ( mine_note.position.position_displacement, - mine_note.volume / 100, + mine_note.volume / 127, ( None if mine_note.percussive diff --git a/Musicreater/data.py b/Musicreater/data.py index 2010007..e9a9e18 100644 --- a/Musicreater/data.py +++ b/Musicreater/data.py @@ -135,7 +135,7 @@ class SingleNote: """Midi 音高""" volume: int - """力度/播放响度 0~100 百分比""" + """力度/播放响度 0~127 百廿七分比""" start_time: int """开始之时 命令刻""" @@ -166,7 +166,7 @@ class SingleNote: midi_pitch: int Midi 音高 note_volume: int - 响度/力度(百分比, 0~100) + 响度/力度(百廿七分比, 0~127) start_time: int 开始之时(命令刻) 注:此处的时间是用从乐曲开始到当前的刻数 @@ -367,7 +367,7 @@ class MineNote: instrument: str """乐器 ID""" volume: float - """力度/播放音量 0~100 百分比""" + """力度/播放音量 0~127 百廿七分比""" start_tick: int """开始之时 命令刻""" duration_tick: int @@ -697,7 +697,8 @@ class SingleMusic(List[SingleTrack]): 归并后的每个元素,按 sort_key 升序 """ if is_subseq_sorted: - return heapq.merge(*tracks, key=sort_key) + # 必须这样处理,不能 return 这个 merge,测试过了 + yield from heapq.merge(*tracks, key=sort_key) else: # 初始化堆 heap_pool: List[Tuple[Any, int, T]] = [] @@ -765,11 +766,11 @@ class SingleMusic(List[SingleTrack]): def get_minenotes( self, start_time: float, end_time: float = inf - ) -> Generator[MineNote, Any, None]: + ) -> Iterator[MineNote]: """获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序""" if self.track_amount == 0: - return - yield from self.yield_from_tracks( + return iter(()) + return self.yield_from_tracks( [track.get_minenotes(start_time, end_time) for track in self.music_tracks], sort_key=lambda x: x.start_tick, ) diff --git a/Musicreater/main.py b/Musicreater/main.py index 085a499..dcd4ff3 100644 --- a/Musicreater/main.py +++ b/Musicreater/main.py @@ -117,9 +117,16 @@ class MusiCreater: if __plugin: return __plugin else: - raise FileFormatNotSupportedError( - "无法找到处理`{}`类型文件的插件".format(fpath.suffix.upper()) - ) + if plg_id: + raise PluginNotFoundError( + "无法找到惟一识别码为`{}`、处理`{}`格式的插件".format( + plg_id, fpath.suffix.upper() + ) + ) + else: + raise FileFormatNotSupportedError( + "无法找到处理`{}`格式的插件".format(fpath.suffix.upper()) + ) @classmethod def import_music( @@ -159,7 +166,7 @@ class MusiCreater: plugin_id: Optional[str] = None, plugin_config: Optional[PluginConfig] = None, ) -> None: - self._get_plugin_within_iousage( + return self._get_plugin_within_iousage( self.__plugin_registry.get_music_output_plugin_by_format, file_path, self.__plugin_registry._music_output_plugins, @@ -173,7 +180,7 @@ class MusiCreater: plugin_id: Optional[str] = None, plugin_config: Optional[PluginConfig] = None, ) -> None: - self._get_plugin_within_iousage( + return self._get_plugin_within_iousage( self.__plugin_registry.get_track_output_plugin_by_format, file_path, self.__plugin_registry._track_output_plugins, diff --git a/Musicreater/plugins.py b/Musicreater/plugins.py index 564d9e0..f3158e2 100644 --- a/Musicreater/plugins.py +++ b/Musicreater/plugins.py @@ -231,6 +231,7 @@ class PluginRegistry: if plugin.can_handle_format(fpath_or_format) ) elif isinstance(fpath_or_format, Path): + # print("在",plugin_regdict,"中,查找可用于处理",fpath_or_format,"的插件") return ( plugin for plugin in plugin_regdict.values() diff --git a/docs/编写插件.md b/docs/编写插件.md index 2097183..bc12e5d 100644 --- a/docs/编写插件.md +++ b/docs/编写插件.md @@ -17,6 +17,7 @@ Email [TriM-Organization@hotmail.com](mailto:TriM-Organization@hotmail.com) 则无需标注原作者,允许该使用者自行署名 本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。 +本声明同样适用于所有直接转载的内容。 ``` 本教程文档的关联文件是: diff --git a/examples/exp_dataexport_plugin.py b/examples/exp_dataexport_plugin.py index 71e59a0..9d80051 100644 --- a/examples/exp_dataexport_plugin.py +++ b/examples/exp_dataexport_plugin.py @@ -22,6 +22,7 @@ Copyright © 2026 Eilles 则无需标注原作者,允许该使用者自行署名 本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。 +本声明同样适用于所有直接转载的内容。 """ from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple diff --git a/examples/exp_importdata_plugin.py b/examples/exp_importdata_plugin.py index 0d990f2..03021ad 100644 --- a/examples/exp_importdata_plugin.py +++ b/examples/exp_importdata_plugin.py @@ -22,6 +22,7 @@ Copyright © 2026 Eilles 则无需标注原作者,允许该使用者自行署名 本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。 +本声明同样适用于所有直接转载的内容。 """ from typing import BinaryIO, Optional diff --git a/old-things/Musicreater/old_plugin/mcstructfile/main.py b/old-things/Musicreater/old_plugin/mcstructfile/main.py index 25c3d2e..8fb2457 100644 --- a/old-things/Musicreater/old_plugin/mcstructfile/main.py +++ b/old-things/Musicreater/old_plugin/mcstructfile/main.py @@ -16,7 +16,7 @@ from typing import Literal from ...old_main import MidiConvert from ...subclass import MineCommand -from ..mcstructure import ( +from Musicreater.builtin_plugins.commands_to_structure.mcstructure import ( COMPABILITY_VERSION_117, COMPABILITY_VERSION_119, commands_to_redstone_delay_structure, diff --git a/test_convert_midi.py b/test_convert_midi.py new file mode 100644 index 0000000..cc3b862 --- /dev/null +++ b/test_convert_midi.py @@ -0,0 +1,34 @@ +# 一个简单的项目实践测试 +from pathlib import Path +from Musicreater import load_plugin_module, MusiCreater +from Musicreater.plugins import _global_plugin_registry + +load_plugin_module("Musicreater.builtin_plugins.midi_read") +load_plugin_module("Musicreater.builtin_plugins.to_commands") +load_plugin_module("Musicreater.builtin_plugins.commands_to_structure") + +from Musicreater.builtin_plugins.midi_read import MidiImportConfig +from Musicreater.builtin_plugins.commands_to_structure import McstructureExportConfig + +print("当前支持的导入格式:", _global_plugin_registry.supported_input_formats()) +print("当前支持的导出格式:", _global_plugin_registry.supported_output_formats()) + +msct = MusiCreater.import_music( + Path("./resources/测试片段.mid"), plugin_config=MidiImportConfig() +) + + +print("全局插件注册表:", _global_plugin_registry) +print("插件缓存字典:", msct._plugin_cache) + + +print(msct.music.music_name) + +print( + "大小、音乐总长:", + msct.export_music( + Path("./output.mcstructure"), + plugin_id="music_to_mcstructure_in_delay_plugin", + plugin_config=McstructureExportConfig(), + ), +) diff --git a/test_read.py b/test_read.py index 4d9d6e9..633603e 100644 --- a/test_read.py +++ b/test_read.py @@ -16,9 +16,9 @@ print(msct.music) # 如果要直接访问插件里面的函数: # 为了确保类型安全,以下方法不建议使用,因为这本质上是越过了 MusiCreater 类而直接执行插件的函数 -print(t := msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), None)) # type: ignore +print(t := msct.midi_to_music_plugin.load(Path("./resources/测试片段.mid"), None)) # type: ignore # 我们建议用这种方式来代替 -t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load( +t = _global_plugin_registry._music_input_plugins["midi_to_music_plugin"].load( Path("./resources/测试片段.mid"), MidiImportConfig( speed_multiplier=1.0, @@ -27,7 +27,7 @@ t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load( # 或者 from Musicreater.plugins import MusicInputPluginBase -if isinstance((p := msct.midi_2_music_plugin), MusicInputPluginBase): +if isinstance((p := msct.midi_to_music_plugin), MusicInputPluginBase): t = p.load(Path("./resources/测试片段.mid"), None) # 但是说实话,既然已经在 MusiCreater 类中提供了