完成 Midi 导入插件移植

This commit is contained in:
2026-02-12 13:24:46 +08:00
parent 2a5ccb8eeb
commit fff8e43f53
23 changed files with 1254 additions and 377 deletions

View File

@@ -182,10 +182,10 @@ class FutureMidiConvertKamiRES(MidiConvert):
raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。")
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
midi_channels: MineNoteChannelType = empty_midi_channels(default_staff=[])
midi_channels: MineNoteChannelType = enumerated_stuff_copy(staff=[])
channel_controler: Dict[int, Dict[str, int]] = empty_midi_channels(
default_staff={
channel_controler: Dict[int, Dict[str, int]] = enumerated_stuff_copy(
staff={
MIDI_PROGRAM: default_program_value,
MIDI_VOLUME: default_volume_value,
MIDI_PAN: 64,
@@ -205,7 +205,7 @@ class FutureMidiConvertKamiRES(MidiConvert):
int,
]
],
] = empty_midi_channels(default_staff=[])
] = enumerated_stuff_copy(staff=[])
note_queue_B: Dict[
int,
List[
@@ -214,7 +214,7 @@ class FutureMidiConvertKamiRES(MidiConvert):
int,
]
],
] = empty_midi_channels(default_staff=[])
] = enumerated_stuff_copy(staff=[])
# 直接使用mido.midifiles.tracks.merge_tracks转为单轨
# 采用的时遍历信息思路
@@ -1042,7 +1042,7 @@ class FutureMidiConvertM5(MidiConvert):
# )
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
midi_channels: ChannelType = empty_midi_channels()
midi_channels: ChannelType = enumerated_stuff_copy()
tempo = 500000
# 我们来用通道统计音乐信息
@@ -1052,7 +1052,7 @@ class FutureMidiConvertM5(MidiConvert):
if not track:
continue
note_queue = empty_midi_channels(default_staff=[])
note_queue = enumerated_stuff_copy(staff=[])
for msg in track:
if msg.time != 0:

View File

@@ -34,14 +34,6 @@ class MSCTBaseException(Exception):
raise self
class MidiFormatException(MSCTBaseException):
"""音·创 的所有MIDI格式错误均继承于此"""
def __init__(self, *args):
"""音·创 的所有MIDI格式错误均继承于此"""
super().__init__("MIDI 格式错误", *args)
class MidiDestroyedError(MSCTBaseException):
"""Midi文件损坏"""
@@ -82,84 +74,3 @@ class CommandFormatError(MSCTBaseException, RuntimeError):
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
class NotDefineTempoError(MidiFormatException):
"""没有Tempo设定导致时间无法计算的错误"""
def __init__(self, *args):
"""没有Tempo设定导致时间无法计算的错误"""
super().__init__("在曲目开始时没有声明 Tempo未指定拍长", *args)
class ChannelOverFlowError(MidiFormatException):
"""一个midi中含有过多的通道"""
def __init__(self, max_channel=16, *args):
"""一个midi中含有过多的通道"""
super().__init__("含有过多的通道(数量应≤{}".format(max_channel), *args)
class NotDefineProgramError(MidiFormatException):
"""没有Program设定导致没有乐器可以选择的错误"""
def __init__(self, *args):
"""没有Program设定导致没有乐器可以选择的错误"""
super().__init__("未指定演奏乐器", *args)
class NoteOnOffMismatchError(MidiFormatException):
"""音符开音和停止不匹配的错误"""
def __init__(self, *args):
"""音符开音和停止不匹配的错误"""
super().__init__("音符不匹配", *args)
class LyricMismatchError(MSCTBaseException):
"""歌词匹配解析错误"""
def __init__(self, *args):
"""有可能产生了错误的歌词解析"""
super().__init__("歌词解析错误", *args)
# 已重构
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
"""以0作为播放速度的错误"""
def __init__(self, *args):
"""以0作为播放速度的错误"""
super().__init__("播放速度为零", *args)
# 已重构
class IllegalMinimumVolumeError(MSCTBaseException, ValueError):
"""最小播放音量有误的错误"""
def __init__(self, *args):
"""最小播放音量错误"""
super().__init__("最小播放音量超出范围", *args)
# 已重构
class MusicSequenceDecodeError(MSCTBaseException):
"""音乐序列解码错误"""
def __init__(self, *args):
"""音乐序列无法正确解码的错误"""
super().__init__("解码音符序列文件时出现问题", *args)
# 已重构
class MusicSequenceTypeError(MSCTBaseException):
"""音乐序列类型错误"""
def __init__(self, *args):
"""无法识别音符序列字节码的类型"""
super().__init__("错误的音符序列字节类型", *args)
# 已重构
class MusicSequenceVerificationFailed(MusicSequenceDecodeError):
"""音乐序列校验失败"""
def __init__(self, *args):
"""音符序列文件与其校验值不一致"""
super().__init__("音符序列文件校验失败", *args)

View File

@@ -170,7 +170,7 @@ class MusicSequence:
pitched_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
percussion_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
minimum_vol: float = 0.1,
volume_processing_function: FittingFunctionType = velocity_2_distance_natural,
volume_processing_function: FittingFunctionType = volume_2_distance_natural,
panning_processing_function: FittingFunctionType = panning_2_rotation_linear,
deviation: float = 0,
note_referance_table_replacement: Dict[str, str] = {},
@@ -273,7 +273,7 @@ class MusicSequence:
8 : (stt_index := 8 + (group_1 >> 10))
].decode("GB18030")
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
channels_: MineNoteChannelType = enumerated_stuff_copy(staff=[])
total_note_count = 0
if verify:
_header_index = stt_index
@@ -415,7 +415,7 @@ class MusicSequence:
_t6_buffer = _t2_buffer = 0
_channel_inst_chart: Dict[str, Dict[str, int]] = {}
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
channels_: MineNoteChannelType = enumerated_stuff_copy(staff=[])
for i in range(total_note_count):
if verify:
@@ -525,7 +525,7 @@ class MusicSequence:
music_name_ = bytes_buffer_in[
8 : (stt_index := 8 + (group_1 >> 10))
].decode("GB18030")
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
channels_: MineNoteChannelType = enumerated_stuff_copy(staff=[])
for channel_index in channels_.keys():
for i in range(
int.from_bytes(
@@ -568,7 +568,7 @@ class MusicSequence:
music_name_ = bytes_buffer_in[
8 : (stt_index := 8 + (group_1 >> 10))
].decode("utf-8")
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
channels_: MineNoteChannelType = enumerated_stuff_copy(staff=[])
for channel_index in channels_.keys():
for i in range(
int.from_bytes(
@@ -820,7 +820,7 @@ class MusicSequence:
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
pitched_note_rtable: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
vol_processing_function: FittingFunctionType = velocity_2_distance_natural,
vol_processing_function: FittingFunctionType = volume_2_distance_natural,
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
note_rtable_replacement: Dict[str, str] = {},
) -> Tuple[MineNoteChannelType, int, Dict[str, int]]:
@@ -860,10 +860,10 @@ class MusicSequence:
raise ZeroSpeedError("播放速度不得为零,应为 (0,1] 范围内的实数。")
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
midi_channels: MineNoteChannelType = empty_midi_channels(default_staff=[])
midi_channels: MineNoteChannelType = enumerated_stuff_copy(staff=[])
channel_controler: Dict[int, Dict[str, int]] = empty_midi_channels(
default_staff={
channel_controler: Dict[int, Dict[str, int]] = enumerated_stuff_copy(
staff={
MIDI_PROGRAM: default_program_value,
MIDI_VOLUME: default_volume_value,
MIDI_PAN: 64,
@@ -883,7 +883,7 @@ class MusicSequence:
int,
]
],
] = empty_midi_channels(default_staff=[])
] = enumerated_stuff_copy(staff=[])
note_queue_B: Dict[
int,
List[
@@ -892,7 +892,7 @@ class MusicSequence:
int,
]
],
] = empty_midi_channels(default_staff=[])
] = enumerated_stuff_copy(staff=[])
lyric_cache: List[Tuple[int, str]] = []
@@ -1099,7 +1099,7 @@ class MidiConvert(MusicSequence):
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
enable_old_exe_format: bool = False,
minimum_volume: float = 0.1,
vol_processing_function: FittingFunctionType = velocity_2_distance_natural,
vol_processing_function: FittingFunctionType = volume_2_distance_natural,
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
pitch_deviation: float = 0,
note_rtable_replacement: Dict[str, str] = {},
@@ -1182,7 +1182,7 @@ class MidiConvert(MusicSequence):
percussion_note_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
old_exe_format: bool = False,
min_volume: float = 0.1,
vol_processing_func: FittingFunctionType = velocity_2_distance_natural,
vol_processing_func: FittingFunctionType = volume_2_distance_natural,
pan_processing_func: FittingFunctionType = panning_2_rotation_linear,
music_pitch_deviation: float = 0,
note_table_replacement: Dict[str, str] = {},

View File

@@ -39,31 +39,15 @@ from Musicreater.constants import (
MM_INSTRUMENT_DEVIATION_TABLE,
MM_INSTRUMENT_RANGE_TABLE,
)
from .old_exceptions import MusicSequenceDecodeError
from Musicreater.exceptions import SingleNoteDecodeError
from Musicreater._utils import enumerated_stuffcopy_dictionary
from Musicreater.builtin_plugins.midi_read.utils import midi_inst_to_mc_sound
from .subclass import MineNote, mctick2timestr, SingleNoteBox
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
def empty_midi_channels(
channel_count: int = 17, default_staff: Any = {}
) -> Dict[int, Any]:
"""
空MIDI通道字典
"""
return dict(
(
i,
(
default_staff.copy()
if isinstance(default_staff, (dict, list))
else default_staff
),
) # 这告诉我们你不能忽略任何一个复制的序列因为它真的我哭死折磨我一整天全在这个bug上了
for i in range(channel_count)
)
def inst_to_sould_with_deviation(
instrumentID: int,
reference_table: MidiInstrumentTableType,
@@ -99,34 +83,6 @@ def inst_to_sould_with_deviation(
)
def midi_inst_to_mc_sound(
instrumentID: int,
reference_table: MidiInstrumentTableType,
default_instrument: str = "note.flute",
) -> str:
"""
返回midi的乐器ID对应的我的世界乐器名
Parameters
----------
instrumentID: int
midi的乐器ID
reference_table: Dict[int, Tuple[str, int]]
转换乐器参照表
default_instrument: str
查无此乐器时的替换乐器
Returns
-------
str我的世界乐器名
"""
return reference_table.get(
instrumentID,
default_instrument,
)
def minenote_to_command_parameters(
mine_note: MineNote,
pitch_deviation: float = 0,
@@ -175,85 +131,6 @@ def minenote_to_command_parameters(
)
def midi_msgs_to_minenote(
inst_: int, # 乐器编号
note_: int,
percussive_: bool, # 是否作为打击乐器启用
volume_: int,
velocity_: int,
panning_: int,
start_time_: int,
duration_: int,
play_speed: float,
midi_reference_table: MidiInstrumentTableType,
volume_processing_method_: FittingFunctionType,
panning_processing_method_: FittingFunctionType,
note_table_replacement: Dict[str, str] = {},
lyric_line: str = "",
) -> MineNote:
"""
将Midi信息转为我的世界音符对象
Parameters
------------
inst_: int
乐器编号
note_: int
音高编号音符编号
percussive_: bool
是否作为打击乐器启用
volume_: int
音量
velocity_: int
力度
panning_: int
声相偏移
start_time_: int
音符起始时间微秒
duration_: int
音符持续时间微秒
play_speed: float
曲目播放速度
midi_reference_table: Dict[int, str]
转换对照表
volume_processing_method_: Callable[[float], float]
音量处理函数
panning_processing_method_: Callable[[float], float]
立体声相偏移处理函数
note_table_replacement: Dict[str, str]
音符替换表定义 Minecraft 音符字串的替换
lyric_line: str
该音符的歌词
Returns
---------
MineNote
我的世界音符对象
"""
mc_sound_ID = midi_inst_to_mc_sound(
inst_,
midi_reference_table,
"note.bd" if percussive_ else "note.flute",
)
return MineNote(
mc_sound_name=note_table_replacement.get(mc_sound_ID, mc_sound_ID),
midi_pitch=note_,
midi_velocity=velocity_,
start_time=(tk := int(start_time_ / float(play_speed) / 50000)),
last_time=round(duration_ / float(play_speed) / 50000),
mass_precision_time=round((start_time_ / float(play_speed) - tk * 50000) / 800),
is_percussion=percussive_,
distance=volume_processing_method_(volume_),
azimuth=(panning_processing_method_(panning_), 0),
extra_information={
"LYRIC_TEXT": lyric_line,
"VOLUME_VALUE": volume_,
"PIN_VALUE": panning_,
},
)
def midi_msgs_to_minenote_using_kami_respack(
inst_: int, # 乐器编号
note_: int,
@@ -549,11 +426,10 @@ def load_decode_fsq_flush_release(
)
except Exception as _err:
# print(bytes_buffer_in[stt_index:end_index])
raise MusicSequenceDecodeError(
_err,
raise SingleNoteDecodeError(
"所截取的音符码之首个字节:",
_first_byte,
)
) from _err
def load_decode_msq_flush_release(
@@ -600,8 +476,8 @@ def load_decode_msq_flush_release(
_total_note_count = 1
_channel_infos = empty_midi_channels(
default_staff={"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
_channel_infos = enumerated_stuffcopy_dictionary(
staff={"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
)
for __channel_index in _channel_infos.keys():
@@ -730,7 +606,7 @@ def load_decode_msq_flush_release(
_total_note_count -= 1
except Exception as _err:
# print(channels_)
raise MusicSequenceDecodeError("难以定位的解码错误", _err)
raise SingleNoteDecodeError("难以定位的解码错误") from _err
if not _read_in_note_list:
break
# _note_list.append