mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2025-01-31 07:23:17 +08:00
完成2.0.0正式版构建,现已支持音乐保存……
This commit is contained in:
parent
77351d767b
commit
f1ab2373b5
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
||||
/*.midi
|
||||
/*.mcpack
|
||||
/*.bdx
|
||||
/*.msq
|
||||
/*.json
|
||||
/*.mcstructure
|
||||
.mscbackup
|
||||
|
@ -17,18 +17,19 @@ Terms & Conditions: License.md in the root directory
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__version__ = "2.0.0-beta"
|
||||
__version__ = "2.0.0"
|
||||
__vername__ = "全新组织架构"
|
||||
__author__ = (
|
||||
("金羿", "Eilles Wan"),
|
||||
("诸葛亮与八卦阵", "bgArray"),
|
||||
("偷吃不是Touch", "Touch"),
|
||||
("鸣凤鸽子", "MingFengPigeon"),
|
||||
("鱼旧梦", "ElapsingDreams"),
|
||||
)
|
||||
__all__ = [
|
||||
# 主要类
|
||||
"MusicSequence",
|
||||
"MidiConvert",
|
||||
"MusicSave",
|
||||
# 附加类
|
||||
"SingleNote",
|
||||
"MineNote",
|
||||
|
@ -467,15 +467,15 @@ MM_INSTRUMENT_DEVIATION_TABLE: Dict[str, int] = {
|
||||
"note.banjo": 6,
|
||||
"note.flute": 18,
|
||||
"note.bass": -18,
|
||||
"note.snare": -1,
|
||||
"note.snare": 0,
|
||||
"note.didgeridoo": -18,
|
||||
"mob.zombie.wood": -1,
|
||||
"mob.zombie.wood": 0,
|
||||
"note.bit": 6,
|
||||
"note.hat": -1,
|
||||
"note.bd": -1,
|
||||
"firework.blast": -1,
|
||||
"firework.twinkle": -1,
|
||||
"fire.ignite": -1,
|
||||
"note.hat": 0,
|
||||
"note.bd": 0,
|
||||
"firework.blast": 0,
|
||||
"firework.twinkle": 0,
|
||||
"fire.ignite": 0,
|
||||
"note.cow_bell": 6,
|
||||
}
|
||||
"""不同乐器的音调偏离对照表"""
|
||||
|
@ -157,7 +157,7 @@ class FutureMidiConvertM4(MidiConvert):
|
||||
*relative_coordinates,
|
||||
volume_percentage,
|
||||
1.0 if note.percussive else mc_pitch,
|
||||
self.minium_volume,
|
||||
self.minimum_volume,
|
||||
)
|
||||
),
|
||||
annotation=(
|
||||
|
@ -88,7 +88,7 @@ class MusicSequence:
|
||||
note_count_per_instrument: Dict[str, int]
|
||||
"""所使用的乐器"""
|
||||
|
||||
minium_volume: float
|
||||
minimum_volume: float
|
||||
"""乐曲最小音量"""
|
||||
|
||||
music_deviation: float
|
||||
@ -100,11 +100,11 @@ class MusicSequence:
|
||||
channels_of_notes: MineNoteChannelType,
|
||||
music_note_count: Optional[int] = None,
|
||||
note_used_per_instrument: Optional[Dict[str, int]] = None,
|
||||
minium_volume_of_music: float = 0.1,
|
||||
minimum_volume_of_music: float = 0.1,
|
||||
deviation_value: Optional[float] = None,
|
||||
) -> None:
|
||||
"""
|
||||
《我的世界》音符序列类
|
||||
音符序列类
|
||||
|
||||
Paramaters
|
||||
==========
|
||||
@ -112,21 +112,27 @@ class MusicSequence:
|
||||
乐曲名称
|
||||
channels_of_notes: MineNoteChannelType
|
||||
音乐音轨
|
||||
minium_volume_of_music: float
|
||||
music_note_count: int
|
||||
总音符数
|
||||
note_used_per_instrument: Dict[str, int]
|
||||
全曲乐器使用统计
|
||||
minimum_volume_of_music: float
|
||||
音乐最小音量(0,1]
|
||||
deviation_value: float
|
||||
全曲音调偏移值
|
||||
"""
|
||||
|
||||
if minium_volume_of_music > 1 or minium_volume_of_music <= 0:
|
||||
if minimum_volume_of_music > 1 or minimum_volume_of_music <= 0:
|
||||
raise IllegalMinimumVolumeError(
|
||||
"自订的最小音量参数错误:{},应在 (0,1] 范围内。".format(
|
||||
minium_volume_of_music
|
||||
minimum_volume_of_music
|
||||
)
|
||||
)
|
||||
# max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
||||
|
||||
self.music_name = name_of_music
|
||||
self.channels = channels_of_notes
|
||||
self.minium_volume = minium_volume_of_music
|
||||
self.minimum_volume = minimum_volume_of_music
|
||||
|
||||
if (note_used_per_instrument is None) or (music_note_count is None):
|
||||
kp = [i.sound_name for j in self.channels.values() for i in j]
|
||||
@ -151,17 +157,43 @@ class MusicSequence:
|
||||
midi_music_name: str,
|
||||
mismatch_error_ignorance: bool = True,
|
||||
speed_multiplier: float = 1,
|
||||
default_tempo: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||
pitched_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||
percussion_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||
minium_vol: float = 0.1,
|
||||
minimum_vol: float = 0.1,
|
||||
volume_processing_function: FittingFunctionType = natural_curve,
|
||||
default_tempo: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||
deviation: float = 0,
|
||||
):
|
||||
"""
|
||||
自mido对象导入一个音符序列类
|
||||
|
||||
Paramaters
|
||||
==========
|
||||
mido_file: mido.MidiFile 对象
|
||||
需要处理的midi对象
|
||||
midi_music_name: str
|
||||
音乐名称
|
||||
mismatch_error_ignorance bool
|
||||
是否在导入时忽略音符不匹配错误
|
||||
speed_multiplier: float
|
||||
音乐播放速度倍数
|
||||
default_tempo: int
|
||||
默认的MIDI TEMPO值
|
||||
pitched_note_referance_table: Dict[int, Tuple[str, int]]
|
||||
乐音乐器Midi-MC对照表
|
||||
percussion_note_referance_table: Dict[int, Tuple[str, int]]
|
||||
打击乐器Midi-MC对照表
|
||||
minimum_vol: float
|
||||
播放的最小音量 应为 (0,1] 范围内的小数
|
||||
volume_processing_function: Callable[[float], float]
|
||||
声像偏移拟合函数
|
||||
deviation: float
|
||||
全曲音调偏移值
|
||||
"""
|
||||
(
|
||||
note_channels,
|
||||
note_count_total,
|
||||
inst_note_count, # qualified_inst_note_count,
|
||||
inst_note_count,
|
||||
) = cls.to_music_note_channels(
|
||||
midi=mido_file,
|
||||
speed=speed_multiplier,
|
||||
@ -176,22 +208,34 @@ class MusicSequence:
|
||||
channels_of_notes=note_channels,
|
||||
music_note_count=note_count_total,
|
||||
note_used_per_instrument=inst_note_count,
|
||||
minium_volume_of_music=minium_vol,
|
||||
minimum_volume_of_music=minimum_vol,
|
||||
deviation_value=deviation,
|
||||
)
|
||||
|
||||
def set_min_volume(self, volume_value: int):
|
||||
self.minium_volume = volume_value
|
||||
"""重新设置全曲最小音量"""
|
||||
if volume_value > 1 or volume_value <= 0:
|
||||
raise IllegalMinimumVolumeError(
|
||||
"自订的最小音量参数错误:{},应在 (0,1] 范围内。".format(volume_value)
|
||||
)
|
||||
self.minimum_volume = volume_value
|
||||
|
||||
def set_deviation(self, deviation_value: int):
|
||||
"""重新设置全曲音调偏移"""
|
||||
self.music_deviation = deviation_value
|
||||
|
||||
def rename_music(self, new_name: str):
|
||||
"""重命名此音乐"""
|
||||
self.music_name = new_name
|
||||
|
||||
def add_note(self, channel_no: int, note: MineNote, is_sort: bool = False):
|
||||
"""在指定通道添加一个音符"""
|
||||
self.channels[channel_no].append(note)
|
||||
self.total_note_count += 1
|
||||
if note.sound_name in self.note_count_per_instrument.keys():
|
||||
self.note_count_per_instrument[note.sound_name] += 1
|
||||
else:
|
||||
self.note_count_per_instrument[note.sound_name] = 1
|
||||
if is_sort:
|
||||
self.channels[channel_no].sort(key=lambda note: note.start_tick)
|
||||
|
||||
@ -247,34 +291,45 @@ class MusicSequence:
|
||||
midi: mido.MidiFile,
|
||||
ignore_mismatch_error: bool = True,
|
||||
speed: float = 1.0,
|
||||
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,
|
||||
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||
vol_processing_function: FittingFunctionType = natural_curve,
|
||||
) -> Tuple[MineNoteChannelType, int, Dict[str, int]]: # , Dict[str, int]]:
|
||||
) -> Tuple[MineNoteChannelType, int, Dict[str, int]]:
|
||||
"""
|
||||
将midi解析并转换为频道音符字典
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi: mido.MidiFile 对象
|
||||
需要处理的midi对象
|
||||
ignore_mismatch_error: bool
|
||||
是否在导入时忽略音符不匹配错误
|
||||
speed: float
|
||||
音乐播放速度倍数
|
||||
default_tempo_value: int
|
||||
默认的MIDI TEMPO值
|
||||
pitched_note_rtable: Dict[int, Tuple[str, int]]
|
||||
乐音乐器Midi-MC对照表
|
||||
percussion_note_rtable: Dict[int, Tuple[str, int]]
|
||||
打击乐器Midi-MC对照表
|
||||
vol_processing_function: Callable[[float], float]
|
||||
声像偏移拟合函数
|
||||
|
||||
Returns
|
||||
-------
|
||||
以频道作为分割的Midi音符列表字典:
|
||||
Dict[int,List[SingleNote,]]
|
||||
以频道作为分割的Midi音符列表字典, 音符总数, 乐器使用统计:
|
||||
Tuple[MineNoteChannelType, int, Dict[str, int]]
|
||||
"""
|
||||
|
||||
if speed == 0:
|
||||
raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。")
|
||||
|
||||
# if midi is None:
|
||||
# raise MidiUnboundError(
|
||||
# "Midi参量为空。你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。"
|
||||
# )
|
||||
|
||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||
midi_channels: MineNoteChannelType = empty_midi_channels(staff=[])
|
||||
tempo = default_tempo_value
|
||||
note_count = 0
|
||||
note_count_per_instrument: Dict[str, int] = {}
|
||||
# qualified_note_count_per_instruments: Dict[str, int] = {}
|
||||
|
||||
# 我们来用通道统计音乐信息
|
||||
# 但是是用分轨的思路的
|
||||
@ -366,14 +421,8 @@ class MusicSequence:
|
||||
note_count += 1
|
||||
if that_note.sound_name in note_count_per_instrument.keys():
|
||||
note_count_per_instrument[that_note.sound_name] += 1
|
||||
# qualified_note_count_per_instruments[
|
||||
# that_note.sound_name
|
||||
# ] += is_note_in_diapason(that_note)
|
||||
else:
|
||||
note_count_per_instrument[that_note.sound_name] = 1
|
||||
# qualified_note_count_per_instruments[
|
||||
# that_note.sound_name
|
||||
# ] = int(is_note_in_diapason(that_note))
|
||||
else:
|
||||
if ignore_mismatch_error:
|
||||
print(
|
||||
@ -411,7 +460,6 @@ class MusicSequence:
|
||||
channels,
|
||||
note_count,
|
||||
note_count_per_instrument,
|
||||
# qualified_note_count_per_instruments,
|
||||
)
|
||||
|
||||
|
||||
@ -442,9 +490,8 @@ class MidiConvert(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,
|
||||
# enable_devation_guess: bool = True,
|
||||
enable_old_exe_format: bool = False,
|
||||
minium_volume: float = 0.1,
|
||||
minimum_volume: float = 0.1,
|
||||
vol_processing_function: FittingFunctionType = natural_curve,
|
||||
):
|
||||
"""
|
||||
@ -454,14 +501,24 @@ class MidiConvert(MusicSequence):
|
||||
----------
|
||||
midi_obj: mido.MidiFile 对象
|
||||
需要处理的midi对象
|
||||
midi_name: MIDI乐曲名称
|
||||
此音乐之名
|
||||
enable_old_exe_format: bool
|
||||
是否启用旧版(≤1.19)指令格式,默认为否
|
||||
midi_name: str
|
||||
此音乐之名称
|
||||
ignore_mismatch_error: bool
|
||||
是否在导入时忽略音符不匹配错误
|
||||
playment_speed: float
|
||||
音乐播放速度倍数
|
||||
default_tempo_value: int
|
||||
默认的MIDI TEMPO值
|
||||
pitched_note_rtable: Dict[int, Tuple[str, int]]
|
||||
乐音乐器Midi-MC对照表
|
||||
percussion_note_rtable: Dict[int, Tuple[str, int]]
|
||||
打击乐器Midi-MC对照表
|
||||
enable_old_exe_format: bool
|
||||
是否启用旧版(≤1.19)指令格式,默认为否
|
||||
minimum_volume: float
|
||||
最小播放音量
|
||||
vol_processing_function: Callable[[float], float]
|
||||
声像偏移拟合函数
|
||||
"""
|
||||
|
||||
cls.enable_old_exe_format: bool = enable_old_exe_format
|
||||
@ -481,10 +538,9 @@ class MidiConvert(MusicSequence):
|
||||
speed_multiplier=playment_speed,
|
||||
pitched_note_referance_table=pitched_note_rtable,
|
||||
percussion_note_referance_table=percussion_note_rtable,
|
||||
minium_vol=minium_volume,
|
||||
minimum_vol=minimum_volume,
|
||||
volume_processing_function=vol_processing_function,
|
||||
default_tempo=default_tempo_value,
|
||||
# devation_guess_enabled=enable_devation_guess,
|
||||
mismatch_error_ignorance=ignore_mismatch_error,
|
||||
)
|
||||
|
||||
@ -497,7 +553,6 @@ class MidiConvert(MusicSequence):
|
||||
default_tempo: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||
pitched_note_table: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||
percussion_note_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||
# devation_guess_enabled: bool = True,
|
||||
old_exe_format: bool = False,
|
||||
min_volume: float = 0.1,
|
||||
vol_processing_func: FittingFunctionType = natural_curve,
|
||||
@ -509,17 +564,22 @@ class MidiConvert(MusicSequence):
|
||||
----------
|
||||
midi_file_path: str
|
||||
midi文件地址
|
||||
|
||||
speed: float
|
||||
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||
mismatch_error_ignorance bool
|
||||
是否在导入时忽略音符不匹配错误
|
||||
play_speed: float
|
||||
音乐播放速度倍数
|
||||
default_tempo: int
|
||||
默认的MIDI TEMPO值
|
||||
pitched_note_table: Dict[int, Tuple[str, int]]
|
||||
乐音乐器Midi-MC对照表
|
||||
percussion_note_table: Dict[int, Tuple[str, int]]
|
||||
打击乐器Midi-MC对照表
|
||||
enable_old_exe_format: bool
|
||||
old_exe_format: bool
|
||||
是否启用旧版(≤1.19)指令格式,默认为否
|
||||
min_volume: float
|
||||
最小播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值
|
||||
最小播放音量
|
||||
vol_processing_func: Callable[[float], float]
|
||||
声像偏移拟合函数
|
||||
"""
|
||||
|
||||
midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0].replace(
|
||||
@ -539,9 +599,8 @@ class MidiConvert(MusicSequence):
|
||||
default_tempo_value=default_tempo,
|
||||
pitched_note_rtable=pitched_note_table,
|
||||
percussion_note_rtable=percussion_note_table,
|
||||
# enable_devation_guess=devation_guess_enabled,
|
||||
enable_old_exe_format=old_exe_format,
|
||||
minium_volume=min_volume,
|
||||
minimum_volume=min_volume,
|
||||
vol_processing_function=vol_processing_func,
|
||||
)
|
||||
except (ValueError, TypeError) as E:
|
||||
@ -549,9 +608,6 @@ class MidiConvert(MusicSequence):
|
||||
except FileNotFoundError as E:
|
||||
raise FileNotFoundError(f"文件{midi_file_path}不存在:{E}")
|
||||
|
||||
# ……真的那么重要吗
|
||||
# 我又几曾何时,知道祂真的会抛下我
|
||||
|
||||
def form_progress_bar(
|
||||
self,
|
||||
max_score: int,
|
||||
@ -564,17 +620,15 @@ class MidiConvert(MusicSequence):
|
||||
Parameters
|
||||
----------
|
||||
max_score: int
|
||||
midi的乐器ID
|
||||
|
||||
最大的积分值
|
||||
scoreboard_name: str
|
||||
所使用的计分板名称
|
||||
|
||||
progressbar_style: ProgressBarStyle
|
||||
此参数详见 ../docs/库的生成与功能文档.md#进度条自定义
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[SingleCommand,]
|
||||
list[MineCommand,]
|
||||
"""
|
||||
pgs_style = progressbar_style.base_style
|
||||
"""用于被替换的进度条原始样式"""
|
||||
@ -665,10 +719,6 @@ class MidiConvert(MusicSequence):
|
||||
)
|
||||
)
|
||||
|
||||
# 那是假的
|
||||
# 一切都并未留下痕迹啊
|
||||
# 那梦又是多么的真实……
|
||||
|
||||
if r"%%t" in pgs_style:
|
||||
result.append(
|
||||
MineCommand(
|
||||
@ -841,7 +891,7 @@ class MidiConvert(MusicSequence):
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
|
||||
tuple( list[list[MineCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
|
||||
"""
|
||||
|
||||
command_channels = []
|
||||
@ -884,7 +934,7 @@ class MidiConvert(MusicSequence):
|
||||
*relative_coordinates,
|
||||
volume_percentage,
|
||||
1.0 if note.percussive else mc_pitch,
|
||||
self.minium_volume,
|
||||
self.minimum_volume,
|
||||
)
|
||||
),
|
||||
annotation=(
|
||||
@ -923,7 +973,7 @@ class MidiConvert(MusicSequence):
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple( list[SingleCommand,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
||||
tuple( list[MineCommand指令,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
||||
"""
|
||||
|
||||
notes_list: List[MineNote] = sorted(
|
||||
@ -962,7 +1012,7 @@ class MidiConvert(MusicSequence):
|
||||
*relative_coordinates,
|
||||
volume_percentage,
|
||||
1.0 if note.percussive else mc_pitch,
|
||||
self.minium_volume,
|
||||
self.minimum_volume,
|
||||
)
|
||||
),
|
||||
annotation=(
|
||||
@ -997,7 +1047,7 @@ class MidiConvert(MusicSequence):
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[Dict[str, List[MineCommand]], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
||||
Tuple[Dict[str, List[MineCommand指令]], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
||||
"""
|
||||
|
||||
notes_list: List[MineNote] = sorted(
|
||||
@ -1048,7 +1098,7 @@ class MidiConvert(MusicSequence):
|
||||
*relative_coordinates,
|
||||
volume_percentage,
|
||||
1.0 if note.percussive else mc_pitch,
|
||||
self.minium_volume,
|
||||
self.minimum_volume,
|
||||
)
|
||||
),
|
||||
annotation=(
|
||||
@ -1082,3 +1132,72 @@ class MidiConvert(MusicSequence):
|
||||
dst.music_command_list = [i.copy() for i in self.music_command_list]
|
||||
dst.progress_bar_command = [i.copy() for i in self.progress_bar_command]
|
||||
return dst
|
||||
|
||||
|
||||
class MusicSave(MusicSequence):
|
||||
|
||||
@classmethod
|
||||
def load_decode(
|
||||
cls,
|
||||
bytes_buffer_in: bytes,
|
||||
):
|
||||
"""从字节码导入音乐序列"""
|
||||
|
||||
group_1 = int.from_bytes(bytes_buffer_in[4:6], "big")
|
||||
music_name_ = bytes_buffer_in[8 : (stt_index := 8 + (group_1 >> 10))].decode(
|
||||
"utf-8"
|
||||
)
|
||||
channels_: MineNoteChannelType = empty_midi_channels(staff=[])
|
||||
for channel_index in channels_.keys():
|
||||
for i in range(
|
||||
int.from_bytes(
|
||||
bytes_buffer_in[stt_index : (stt_index := stt_index + 4)], "big"
|
||||
)
|
||||
):
|
||||
try:
|
||||
end_index = stt_index + 14 + (bytes_buffer_in[stt_index] >> 2)
|
||||
channels_[channel_index].append(
|
||||
MineNote.decode(bytes_buffer_in[stt_index:end_index])
|
||||
)
|
||||
stt_index = end_index
|
||||
except:
|
||||
print(channels_)
|
||||
raise
|
||||
|
||||
return cls(
|
||||
name_of_music=music_name_,
|
||||
channels_of_notes=channels_,
|
||||
minimum_volume_of_music=(group_1 & 0b1111111111) / 1000,
|
||||
deviation_value=int.from_bytes(bytes_buffer_in[6:8], "big", signed=True)
|
||||
/ 1000,
|
||||
)
|
||||
|
||||
def encode_dump(
|
||||
self,
|
||||
) -> bytes:
|
||||
"""将音乐序列转为二进制字节码"""
|
||||
|
||||
# 音乐名称长度 6 位 支持到 63
|
||||
# 最小音量 minimum_volume 10 位 最大支持 1023 即三位小数
|
||||
# 共 16 位 合 2 字节
|
||||
# +++
|
||||
# 总音调偏移 music_deviation 16 位 最大支持 -32768 ~ 32767 即 三位小数
|
||||
# 共 16 位 合 2 字节
|
||||
# +++
|
||||
# 音乐名称 music_name 长度最多63 支持到 21 个中文字符 或 63 个西文字符
|
||||
bytes_buffer = (
|
||||
b"MSQ#"
|
||||
+ (
|
||||
(len(r := self.music_name.encode("utf-8")) << 10)
|
||||
+ round(self.minimum_volume * 1000)
|
||||
).to_bytes(2, "big")
|
||||
+ round(self.music_deviation * 1000).to_bytes(2, "big", signed=True)
|
||||
+ r
|
||||
)
|
||||
|
||||
for channel_index, note_list in self.channels.items():
|
||||
bytes_buffer += len(note_list).to_bytes(4, "big")
|
||||
for note_ in note_list:
|
||||
bytes_buffer += note_.encode()
|
||||
|
||||
return bytes_buffer
|
||||
|
@ -1,483 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
旧版本转换功能以及已经弃用的函数
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
from .exceptions import *
|
||||
from .main import MidiConvert, mido
|
||||
from .subclass import *
|
||||
from .types import ChannelType
|
||||
from .utils import *
|
||||
from .constants import *
|
||||
|
||||
|
||||
class ObsoleteMidiConvert(MidiConvert):
|
||||
"""
|
||||
我说一句话:
|
||||
这些破烂老代码能跑得起来就是谢天谢地,你们还指望我怎么样?这玩意真的不会再维护了,我发誓!
|
||||
"""
|
||||
|
||||
def to_music_channels(
|
||||
self,
|
||||
) -> ChannelType:
|
||||
"""
|
||||
使用金羿的转换思路,将midi解析并转换为频道信息字典
|
||||
|
||||
Returns
|
||||
-------
|
||||
以频道作为分割的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: ChannelType = empty_midi_channels()
|
||||
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||
|
||||
# 我们来用通道统计音乐信息
|
||||
# 但是是用分轨的思路的
|
||||
for track_no, track in enumerate(self.midi.tracks):
|
||||
microseconds = 0
|
||||
if not track:
|
||||
continue
|
||||
|
||||
note_queue = empty_midi_channels(staff=[])
|
||||
|
||||
for msg in track:
|
||||
if msg.time != 0:
|
||||
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 not track_no in midi_channels[msg.channel].keys():
|
||||
midi_channels[msg.channel][track_no] = []
|
||||
except AttributeError as E:
|
||||
print(msg, E)
|
||||
|
||||
if msg.type == "program_change":
|
||||
midi_channels[msg.channel][track_no].append(
|
||||
("PgmC", msg.program, microseconds)
|
||||
)
|
||||
|
||||
elif msg.type == "note_on" and msg.velocity != 0:
|
||||
midi_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"
|
||||
):
|
||||
midi_channels[msg.channel][track_no].append(
|
||||
("NoteE", msg.note, microseconds)
|
||||
)
|
||||
|
||||
"""整合后的音乐通道格式
|
||||
每个通道包括若干消息元素其中逃不过这三种:
|
||||
|
||||
1 切换乐器消息
|
||||
("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
|
||||
|
||||
2 音符开始消息
|
||||
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
|
||||
|
||||
3 音符结束消息
|
||||
("NoteE", 结束的音符ID, 距离演奏开始的毫秒)"""
|
||||
del tempo, self.channels
|
||||
self.channels = midi_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_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 = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
else:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
|
||||
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_m1(
|
||||
self, scoreboardname: str = "mscplay", volume: float = 1.0, speed: float = 1.0
|
||||
) -> list:
|
||||
"""
|
||||
使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表
|
||||
:param scoreboardname: 我的世界的计分板名称
|
||||
:param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
||||
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||
:return: tuple(命令列表, 命令个数, 计分板最大值)
|
||||
"""
|
||||
tracks = []
|
||||
if volume > 1:
|
||||
volume = 1
|
||||
if volume <= 0:
|
||||
volume = 0.001
|
||||
|
||||
commands = 0
|
||||
maxscore = 0
|
||||
|
||||
for i, track in enumerate(self.midi.tracks): # type:ignore
|
||||
ticks = 0
|
||||
instrumentID = 0
|
||||
singleTrack = []
|
||||
|
||||
for msg in track:
|
||||
ticks += msg.time
|
||||
# print(msg)
|
||||
if msg.is_meta:
|
||||
if msg.type == "set_tempo":
|
||||
tempo = msg.tempo
|
||||
else:
|
||||
if msg.type == "program_change":
|
||||
# print("TT")
|
||||
instrumentID = msg.program
|
||||
if msg.type == "note_on" and msg.velocity != 0:
|
||||
nowscore = round(
|
||||
(ticks * tempo) / ((self.midi.ticks_per_beat * float(speed)) * 50000) # type: ignore
|
||||
)
|
||||
maxscore = max(maxscore, nowscore)
|
||||
if msg.channel == 9:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
else:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
singleTrack.append(
|
||||
"execute @a[scores={"
|
||||
+ str(scoreboardname)
|
||||
+ "="
|
||||
+ str(nowscore)
|
||||
+ "}"
|
||||
+ f"] ~ ~ ~ playsound {soundID} @s ~ ~{1 / volume - 1} ~ {msg.velocity * (0.7 if msg.channel == 0 else 0.9)} {2 ** ((msg.note - 60 - _X) / 12)}"
|
||||
)
|
||||
commands += 1
|
||||
if len(singleTrack) != 0:
|
||||
tracks.append(singleTrack)
|
||||
|
||||
return [tracks, commands, maxscore]
|
||||
|
||||
# 原本这个算法的转换效果应该和上面的算法相似的
|
||||
def _toCmdList_m2(
|
||||
self,
|
||||
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(): # type: ignore
|
||||
for msg in track:
|
||||
if msg[0] == "PgmC":
|
||||
InstID = msg[1]
|
||||
|
||||
elif msg[0] == "NoteS":
|
||||
soundID, _X = (
|
||||
inst_to_sould_with_deviation(
|
||||
msg[1], MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
if SpecialBits
|
||||
else inst_to_sould_with_deviation(
|
||||
InstID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
)
|
||||
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,
|
||||
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)
|
||||
)
|
||||
|
||||
if msg.channel == 9:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
else:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
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,
|
||||
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(): # type: ignore
|
||||
for msg in track:
|
||||
if msg[0] == "PgmC":
|
||||
InstID = msg[1]
|
||||
|
||||
elif msg[0] == "NoteS":
|
||||
soundID, _X = (
|
||||
inst_to_sould_with_deviation(
|
||||
msg[1], MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
if SpecialBits
|
||||
else inst_to_sould_with_deviation(
|
||||
InstID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
)
|
||||
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)]
|
@ -104,10 +104,124 @@ class MineNote:
|
||||
|
||||
self.extra_info = extra_information
|
||||
|
||||
# @property
|
||||
# def get_mc_pitch(self,table: Dict[int, Tuple[str, int]]) -> float:
|
||||
# self.mc_sound_ID, _X = inst_to_sould_with_deviation(self.inst,table,"note.bd" if self.percussive else "note.flute",)
|
||||
# return -1 if self.percussive else 2 ** ((self.note - 60 - _X) / 12)
|
||||
@classmethod
|
||||
def decode(cls, code_buffer: bytes):
|
||||
"""自字节码析出MineNote类"""
|
||||
group_1 = int.from_bytes(code_buffer[:6], "big")
|
||||
percussive_ = bool(group_1 & 0b1)
|
||||
duration_ = (group_1 := group_1 >> 1) & 0b11111111111111111
|
||||
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
|
||||
note_pitch_ = (group_1 := group_1 >> 17) & 0b1111111
|
||||
sound_name_length = group_1 >> 7
|
||||
|
||||
if code_buffer[6] & 0b1:
|
||||
position_displacement_ = (
|
||||
int.from_bytes(
|
||||
code_buffer[8 + sound_name_length : 10 + sound_name_length],
|
||||
"big",
|
||||
)
|
||||
/ 1000,
|
||||
int.from_bytes(
|
||||
code_buffer[10 + sound_name_length : 12 + sound_name_length],
|
||||
"big",
|
||||
)
|
||||
/ 1000,
|
||||
int.from_bytes(
|
||||
code_buffer[12 + sound_name_length : 14 + sound_name_length],
|
||||
"big",
|
||||
)
|
||||
/ 1000,
|
||||
)
|
||||
else:
|
||||
position_displacement_ = (0, 0, 0)
|
||||
|
||||
try:
|
||||
return cls(
|
||||
mc_sound_name=code_buffer[8 : 8 + sound_name_length].decode(
|
||||
encoding="utf-8"
|
||||
),
|
||||
midi_pitch=note_pitch_,
|
||||
midi_velocity=code_buffer[6] >> 1,
|
||||
start_time=start_tick_,
|
||||
last_time=duration_,
|
||||
track_number=code_buffer[7],
|
||||
is_percussion=percussive_,
|
||||
displacement=position_displacement_,
|
||||
)
|
||||
except:
|
||||
print(code_buffer, "\n", code_buffer[8 : 8 + sound_name_length])
|
||||
raise
|
||||
|
||||
def encode(self, is_displacement_included: bool = True) -> bytes:
|
||||
"""
|
||||
将数据打包为字节码
|
||||
|
||||
:param is_displacement_included:`bool` 是否包含声像偏移数据,默认为**是**
|
||||
|
||||
:return bytes 打包好的字节码
|
||||
"""
|
||||
|
||||
# 字符串长度 6 位 支持到 63
|
||||
# note_pitch 7 位 支持到 127
|
||||
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
||||
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
||||
# percussive 长度 1 位 支持到 1
|
||||
# 共 48 位 合 6 字节
|
||||
# +++
|
||||
# velocity 长度 7 位 支持到 127
|
||||
# is_displacement_included 长度 1 位 支持到 1
|
||||
# 共 8 位 合 1 字节
|
||||
# +++
|
||||
# track_no 长度 8 位 支持到 255 合 1 字节
|
||||
# +++
|
||||
# sound_name 长度最多63 支持到 21 个中文字符 或 63 个西文字符
|
||||
# +++
|
||||
# position_displacement 每个元素长 16 位 合 2 字节
|
||||
# 共 48 位 合 6 字节 支持存储三位小数和两位整数,其值必须在 [0, 65.535] 之间
|
||||
|
||||
return (
|
||||
(
|
||||
(
|
||||
(
|
||||
(
|
||||
(
|
||||
(
|
||||
(
|
||||
(
|
||||
len(
|
||||
r := self.sound_name.encode(
|
||||
encoding="utf-8"
|
||||
)
|
||||
)
|
||||
<< 7
|
||||
)
|
||||
+ self.note_pitch
|
||||
)
|
||||
<< 17
|
||||
)
|
||||
+ self.start_tick
|
||||
)
|
||||
<< 17
|
||||
)
|
||||
+ self.duration
|
||||
)
|
||||
<< 1
|
||||
)
|
||||
+ self.percussive
|
||||
).to_bytes(6, "big")
|
||||
+ ((self.velocity << 1) + is_displacement_included).to_bytes(1, "big")
|
||||
+ self.track_no.to_bytes(1, "big")
|
||||
+ r
|
||||
+ (
|
||||
(
|
||||
round(self.position_displacement[0] * 1000).to_bytes(2, "big")
|
||||
+ round(self.position_displacement[1] * 1000).to_bytes(2, "big")
|
||||
+ round(self.position_displacement[2] * 1000).to_bytes(2, "big")
|
||||
)
|
||||
if is_displacement_included
|
||||
else b""
|
||||
)
|
||||
)
|
||||
|
||||
def set_info(self, sth: Any):
|
||||
"""设置附加信息"""
|
||||
|
@ -42,7 +42,9 @@ Midi乐器对照表类型
|
||||
"""
|
||||
|
||||
FittingFunctionType = Callable[[float], float]
|
||||
|
||||
"""
|
||||
声像偏移音量拟合函数类型
|
||||
"""
|
||||
|
||||
ChannelType = Dict[
|
||||
int,
|
||||
|
@ -74,10 +74,12 @@ def inst_to_sould_with_deviation(
|
||||
midi的乐器ID
|
||||
reference_table: Dict[int, Tuple[str, int]]
|
||||
转换乐器参照表
|
||||
default_instrument: str
|
||||
查无此乐器时的替换乐器
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple(str我的世界乐器名, int转换算法中的X)
|
||||
tuple(str我的世界乐器名, int转换算法中的偏移量)
|
||||
"""
|
||||
sound_id = midi_inst_to_mc_sound(
|
||||
instrumentID=instrumentID,
|
||||
@ -106,6 +108,8 @@ def midi_inst_to_mc_sound(
|
||||
midi的乐器ID
|
||||
reference_table: Dict[int, Tuple[str, int]]
|
||||
转换乐器参照表
|
||||
default_instrument: str
|
||||
查无此乐器时的替换乐器
|
||||
|
||||
Returns
|
||||
-------
|
||||
@ -274,14 +278,14 @@ def midi_msgs_to_minenote(
|
||||
mc_distance_volume = volume_processing_method_(velocity_)
|
||||
|
||||
return MineNote(
|
||||
mc_sound_ID,
|
||||
note_,
|
||||
velocity_,
|
||||
round(start_time_ / float(play_speed) / 50),
|
||||
round(duration_ / float(play_speed) / 50),
|
||||
track_no_,
|
||||
percussive_,
|
||||
(0, mc_distance_volume, 0),
|
||||
mc_sound_name=mc_sound_ID,
|
||||
midi_pitch=note_,
|
||||
midi_velocity=velocity_,
|
||||
start_time=round(start_time_ / float(play_speed) / 50),
|
||||
last_time=round(duration_ / float(play_speed) / 50),
|
||||
track_number=track_no_,
|
||||
is_percussion=percussive_,
|
||||
displacement=(0, mc_distance_volume, 0),
|
||||
)
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user