mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2025-02-25 03:49:41 +08:00
2.0.0全新版本,更好的代码组织形式
This commit is contained in:
parent
3831c41b9a
commit
95c0ff1b47
@ -17,8 +17,8 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__version__ = "1.8"
|
__version__ = "2.0.0-alpha"
|
||||||
__vername__ = "音调总体偏移的处理支持"
|
__vername__ = "全新组织架构"
|
||||||
__author__ = (
|
__author__ = (
|
||||||
("金羿", "Eilles Wan"),
|
("金羿", "Eilles Wan"),
|
||||||
("诸葛亮与八卦阵", "bgArray"),
|
("诸葛亮与八卦阵", "bgArray"),
|
||||||
@ -31,7 +31,8 @@ __all__ = [
|
|||||||
"MidiConvert",
|
"MidiConvert",
|
||||||
# 附加类
|
# 附加类
|
||||||
"SingleNote",
|
"SingleNote",
|
||||||
"SingleCommand",
|
"MineNote",
|
||||||
|
"MineCommand",
|
||||||
"SingleNoteBox",
|
"SingleNoteBox",
|
||||||
# "TimeStamp", 未来功能
|
# "TimeStamp", 未来功能
|
||||||
# 默认值
|
# 默认值
|
||||||
|
@ -414,28 +414,28 @@ MC_INSTRUMENT_BLOCKS_TABLE: Dict[str, Tuple[str, ...]] = {
|
|||||||
|
|
||||||
# Midi对MC通用对照表
|
# Midi对MC通用对照表
|
||||||
|
|
||||||
MM_INSTRUMENT_RANGE_TABLE: Dict[str, Tuple[int, int]] = {
|
MM_INSTRUMENT_RANGE_TABLE: Dict[str, Tuple[Tuple[int, int], int]] = {
|
||||||
"note.harp": (42, 66),
|
"note.harp": ((42, 66), 54),
|
||||||
"note.pling": (42, 66),
|
"note.pling": ((42, 66), 54),
|
||||||
"note.guitar": (30, 54),
|
"note.guitar": ((30, 54), 42),
|
||||||
"note.iron_xylophone": (42, 66),
|
"note.iron_xylophone": ((42, 66), 54),
|
||||||
"note.bell": (66, 90),
|
"note.bell": ((66, 90), 78),
|
||||||
"note.xylophone": (66, 90),
|
"note.xylophone": ((66, 90), 78),
|
||||||
"note.chime": (66, 90),
|
"note.chime": ((66, 90), 78),
|
||||||
"note.banjo": (42, 66),
|
"note.banjo": ((42, 66), 54),
|
||||||
"note.flute": (54, 78),
|
"note.flute": ((54, 78), 66),
|
||||||
"note.bass": (18, 42),
|
"note.bass": ((18, 42), 30),
|
||||||
"note.snare": (-1, 128), # 实际上是 0~127
|
"note.snare": ((-1, 128), 0), # 实际上是 0~127
|
||||||
"note.didgeridoo": (18, 42),
|
"note.didgeridoo": ((18, 42), 30),
|
||||||
"mob.zombie.wood": (-1, 128),
|
"mob.zombie.wood": ((-1, 128), 0),
|
||||||
"note.bit": (42, 66),
|
"note.bit": ((42, 66), 54),
|
||||||
"note.hat": (-1, 128),
|
"note.hat": ((-1, 128), 0),
|
||||||
"note.bd": (-1, 128),
|
"note.bd": ((-1, 128), 0),
|
||||||
"note.basedrum": (-1, 128),
|
"note.basedrum": ((-1, 128), 0),
|
||||||
"firework.blast": (-1, 128),
|
"firework.blast": ((-1, 128), 0),
|
||||||
"firework.twinkle": (-1, 128),
|
"firework.twinkle": ((-1, 128), 0),
|
||||||
"fire.ignite": (-1, 128),
|
"fire.ignite": ((-1, 128), 0),
|
||||||
"note.cow_bell": (54, 78),
|
"note.cow_bell": ((54, 78), 66),
|
||||||
}
|
}
|
||||||
"""不同乐器的音域偏离对照表"""
|
"""不同乐器的音域偏离对照表"""
|
||||||
|
|
||||||
|
@ -115,3 +115,11 @@ class ZeroSpeedError(ZeroDivisionError):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""以0作为播放速度的错误"""
|
"""以0作为播放速度的错误"""
|
||||||
super().__init__("播放速度为0", *args)
|
super().__init__("播放速度为0", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class IllegalMinimumVolumeError(ValueError):
|
||||||
|
"""最小播放音量有误的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""最小播放音量错误"""
|
||||||
|
super().__init__("最小播放音量超出范围", *args)
|
@ -95,19 +95,13 @@ class FutureMidiConvertM4(MidiConvert):
|
|||||||
|
|
||||||
def to_command_list_in_delay(
|
def to_command_list_in_delay(
|
||||||
self,
|
self,
|
||||||
max_volume: float = 1.0,
|
|
||||||
speed: float = 1.0,
|
|
||||||
player_selector: str = "@a",
|
player_selector: str = "@a",
|
||||||
) -> Tuple[List[SingleCommand], int, int]:
|
) -> Tuple[List[MineCommand], int, int]:
|
||||||
"""
|
"""
|
||||||
将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
max_volume: float
|
|
||||||
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
|
||||||
speed: float
|
|
||||||
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
|
||||||
player_selector: str
|
player_selector: str
|
||||||
玩家选择器,默认为`@a`
|
玩家选择器,默认为`@a`
|
||||||
|
|
||||||
@ -116,17 +110,13 @@ class FutureMidiConvertM4(MidiConvert):
|
|||||||
tuple( list[SingleCommand,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
tuple( list[SingleCommand,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if speed == 0:
|
notes_list: List[MineNote] = []
|
||||||
raise ZeroSpeedError("播放速度仅可为(0,1]范围内的正实数")
|
|
||||||
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
|
||||||
|
|
||||||
notes_list: List[SingleNote] = []
|
|
||||||
|
|
||||||
# 此处 我们把通道视为音轨
|
# 此处 我们把通道视为音轨
|
||||||
for channel in self.channels.values():
|
for channel in self.channels.values():
|
||||||
for note in channel:
|
for note in channel:
|
||||||
note.set_info(
|
note.set_info(
|
||||||
note_to_command_parameters(
|
single_note_to_note_parameters(
|
||||||
note,
|
note,
|
||||||
(
|
(
|
||||||
self.percussion_note_referrence_table
|
self.percussion_note_referrence_table
|
||||||
@ -160,7 +150,7 @@ class FutureMidiConvertM4(MidiConvert):
|
|||||||
max_multi = max(max_multi, multi)
|
max_multi = max(max_multi, multi)
|
||||||
multi = 0
|
multi = 0
|
||||||
self.music_command_list.append(
|
self.music_command_list.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(player_selector)
|
self.execute_cmd_head.format(player_selector)
|
||||||
+ r"playsound {} @s ^ ^ ^{} {} {}".format(*note.extra_info),
|
+ r"playsound {} @s ^ ^ ^{} {} {}".format(*note.extra_info),
|
||||||
tick_delay=tickdelay,
|
tick_delay=tickdelay,
|
||||||
@ -173,8 +163,7 @@ class FutureMidiConvertM4(MidiConvert):
|
|||||||
)
|
)
|
||||||
delaytime_previous = delaytime_now
|
delaytime_previous = delaytime_now
|
||||||
|
|
||||||
self.music_tick_num = round(notes_list[-1].start_time / speed / 50)
|
return self.music_command_list, round(notes_list[-1].start_time / speed / 50), max_multi + 1
|
||||||
return self.music_command_list, self.music_tick_num, max_multi + 1
|
|
||||||
|
|
||||||
|
|
||||||
class FutureMidiConvertM5(MidiConvert):
|
class FutureMidiConvertM5(MidiConvert):
|
||||||
@ -267,7 +256,7 @@ class FutureMidiConvertM5(MidiConvert):
|
|||||||
max_volume: float = 1.0,
|
max_volume: float = 1.0,
|
||||||
speed: float = 1.0,
|
speed: float = 1.0,
|
||||||
player_selector: str = "@a",
|
player_selector: str = "@a",
|
||||||
) -> Tuple[List[SingleCommand], int]:
|
) -> Tuple[List[MineCommand], int]:
|
||||||
"""
|
"""
|
||||||
使用金羿的转换思路,使用同刻偏移算法优化音感后,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
使用金羿的转换思路,使用同刻偏移算法优化音感后,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||||
|
|
||||||
@ -314,11 +303,11 @@ class FutureMidiConvertM5(MidiConvert):
|
|||||||
|
|
||||||
elif msg[0] == "NoteS":
|
elif msg[0] == "NoteS":
|
||||||
soundID = (
|
soundID = (
|
||||||
midi_inst_to_mc_sould(
|
midi_inst_to_mc_sound(
|
||||||
msg[1], MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
msg[1], MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||||
)
|
)
|
||||||
if SpecialBits
|
if SpecialBits
|
||||||
else midi_inst_to_mc_sould(
|
else midi_inst_to_mc_sound(
|
||||||
InstID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
InstID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -354,7 +343,7 @@ class FutureMidiConvertM5(MidiConvert):
|
|||||||
for i in range(len(all_ticks)):
|
for i in range(len(all_ticks)):
|
||||||
for j in range(len(tracks[all_ticks[i]])):
|
for j in range(len(tracks[all_ticks[i]])):
|
||||||
results.append(
|
results.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
tracks[all_ticks[i]][j],
|
tracks[all_ticks[i]][j],
|
||||||
tick_delay=(
|
tick_delay=(
|
||||||
(
|
(
|
||||||
@ -402,8 +391,7 @@ class FutureMidiConvertM5(MidiConvert):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.music_command_list = results
|
self.music_command_list = results
|
||||||
self.music_tick_num = max(all_ticks)
|
return results, max(all_ticks)
|
||||||
return results, self.music_tick_num
|
|
||||||
|
|
||||||
|
|
||||||
class FutureMidiConvertM6(MidiConvert):
|
class FutureMidiConvertM6(MidiConvert):
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
音·创 (Musicreater)
|
音·创 (Musicreater)
|
||||||
是一款免费开源的针对《我的世界》的midi音乐转换库
|
一款免费开源的针对《我的世界》音乐的支持库
|
||||||
Musicreater (音·创)
|
Musicreater (音·创)
|
||||||
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
|
A free open source library used for **Minecraft** musics.
|
||||||
|
|
||||||
版权所有 © 2024 音·创 开发者
|
版权所有 © 2024 音·创 开发者
|
||||||
Copyright © 2024 all the developers of Musicreater
|
Copyright © 2024 all the developers of Musicreater
|
||||||
@ -19,10 +19,10 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
# BUG退散!BUG退散! BUG退散!BUG退散!
|
# BUG退散!BUG退散! BUG退散!BUG退散! BUG retreat! BUG retreat!
|
||||||
# 异常、错误作乱之时 異常、誤りが、困った時は
|
# 异常与错误作乱之时 異常、誤りが、困った時は Abnormalities and errors are causing chaos
|
||||||
# 二六字组!万国码合!二六字组!万国码合! グループ!コード#!グループ!コード#!
|
# 二六字组!万国码合!二六字组!万国码合! グループ!コード#!グループ!コード#! Words combine! Unicode unite!
|
||||||
# 赶快呼叫 程序员!Let's Go! 直ぐに呼びましょプログラマ レッツゴー!
|
# 赶快呼叫 程序员!Let's Go! 直ぐに呼びましょプログラマ レッツゴー! Hurry to call the programmer! Let's Go!
|
||||||
|
|
||||||
|
|
||||||
import math
|
import math
|
||||||
@ -84,49 +84,192 @@ class MusicSequence:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
music_name: str
|
music_name: str
|
||||||
"""Midi乐曲名"""
|
"""乐曲名"""
|
||||||
|
|
||||||
channels: NoteChannelType
|
channels: MineNoteChannelType
|
||||||
"""频道信息字典"""
|
"""频道信息字典"""
|
||||||
|
|
||||||
|
total_note_count: int
|
||||||
|
"""音符总数"""
|
||||||
|
|
||||||
|
used_instrument: List[str]
|
||||||
|
"""所使用的乐器"""
|
||||||
|
|
||||||
|
minium_volume: float
|
||||||
|
"""乐曲最小音量"""
|
||||||
|
|
||||||
|
music_deviation: float
|
||||||
|
"""乐曲音调偏移"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name_of_music: str,
|
name_of_music: str,
|
||||||
channels_of_notes: NoteChannelType,
|
channels_of_notes: MineNoteChannelType,
|
||||||
|
music_note_count: Optional[int] = None,
|
||||||
|
used_instrument_of_music: Optional[List[str]] = None,
|
||||||
|
minium_volume_of_music: float = 0.1,
|
||||||
|
deviation: Optional[float] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
《我的世界》音符序列类
|
||||||
|
|
||||||
|
Paramaters
|
||||||
|
==========
|
||||||
|
name_of_music: str
|
||||||
|
乐曲名称
|
||||||
|
channels_of_notes: MineNoteChannelType
|
||||||
|
音乐音轨
|
||||||
|
minium_volume_of_music: float
|
||||||
|
音乐最小音量(0,1]
|
||||||
|
"""
|
||||||
|
|
||||||
|
if minium_volume_of_music > 1 or minium_volume_of_music <= 0:
|
||||||
|
raise IllegalMinimumVolumeError(
|
||||||
|
"自订的最小音量参数错误:{},应在 (0,1] 范围内。".format(
|
||||||
|
minium_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.music_name = name_of_music
|
||||||
self.channels = channels_of_notes
|
self.channels = channels_of_notes
|
||||||
|
self.minium_volume = minium_volume_of_music
|
||||||
|
|
||||||
|
if used_instrument_of_music is None or music_note_count is None:
|
||||||
|
kp = [i.sound_name for j in self.channels.values() for i in j]
|
||||||
|
self.total_note_count = (
|
||||||
|
len(kp) if music_note_count is None else music_note_count
|
||||||
|
)
|
||||||
|
self.used_instrument = (
|
||||||
|
list(set(kp))
|
||||||
|
if used_instrument_of_music is None
|
||||||
|
else used_instrument_of_music
|
||||||
|
)
|
||||||
|
|
||||||
|
self.music_deviation = (
|
||||||
|
self.guess_deviation(
|
||||||
|
self.total_note_count,
|
||||||
|
len(self.used_instrument),
|
||||||
|
music_channels=self.channels,
|
||||||
|
)
|
||||||
|
if deviation is None
|
||||||
|
else deviation
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_mido(
|
def from_mido(
|
||||||
cls,
|
cls,
|
||||||
mido_file: mido.MidiFile,
|
mido_file: mido.MidiFile,
|
||||||
midi_music_name,
|
midi_music_name: str,
|
||||||
default_tempo: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
|
||||||
mismatch_error_ignorance: bool = True,
|
mismatch_error_ignorance: bool = True,
|
||||||
|
speed_multiplier: float = 1,
|
||||||
|
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,
|
||||||
|
volume_processing_function: FittingFunctionType = natural_curve,
|
||||||
|
default_tempo: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||||
|
devation_guess_enabled: bool = True,
|
||||||
):
|
):
|
||||||
|
note_channels, note_count_total, inst_note_count, qualified_inst_note_count = (
|
||||||
return cls(
|
|
||||||
midi_music_name,
|
|
||||||
cls.to_music_note_channels(
|
cls.to_music_note_channels(
|
||||||
midi=mido_file,
|
midi=mido_file,
|
||||||
|
speed=speed_multiplier,
|
||||||
|
pitched_note_rtable=pitched_note_referance_table,
|
||||||
|
percussion_note_rtable=percussion_note_referance_table,
|
||||||
default_tempo_value=default_tempo,
|
default_tempo_value=default_tempo,
|
||||||
|
vol_processing_function=volume_processing_function,
|
||||||
ignore_mismatch_error=mismatch_error_ignorance,
|
ignore_mismatch_error=mismatch_error_ignorance,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return cls(
|
||||||
|
name_of_music=midi_music_name,
|
||||||
|
channels_of_notes=note_channels,
|
||||||
|
music_note_count=note_count_total,
|
||||||
|
used_instrument_of_music=list(inst_note_count.keys()),
|
||||||
|
minium_volume_of_music=minium_vol,
|
||||||
|
deviation=(
|
||||||
|
cls.guess_deviation(
|
||||||
|
note_count_total,
|
||||||
|
len(inst_note_count),
|
||||||
|
inst_note_count,
|
||||||
|
qualified_inst_note_count,
|
||||||
|
)
|
||||||
|
if devation_guess_enabled
|
||||||
|
else 0
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_min_volume(self, volume_value: int):
|
||||||
|
self.minium_volume = volume_value
|
||||||
|
|
||||||
def set_deviation(self, deviation_value: int):
|
def set_deviation(self, deviation_value: int):
|
||||||
self.music_deviation = deviation_value
|
self.music_deviation = deviation_value
|
||||||
|
|
||||||
def rename_music(self, new_name: str):
|
def rename_music(self, new_name: str):
|
||||||
self.music_name = new_name
|
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 is_sort:
|
||||||
|
self.channels[channel_no].sort(key=lambda note: note.start_tick)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def guess_deviation(
|
||||||
|
total_note_count: int,
|
||||||
|
total_instrument_count: int,
|
||||||
|
note_count_per_instruments: Optional[Dict[str, int]] = None,
|
||||||
|
qualified_note_count_per_instruments: Optional[Dict[str, int]] = None,
|
||||||
|
music_channels: Optional[MineNoteChannelType] = None,
|
||||||
|
) -> float:
|
||||||
|
if (
|
||||||
|
note_count_per_instruments is None
|
||||||
|
or qualified_note_count_per_instruments is None
|
||||||
|
):
|
||||||
|
if music_channels is None:
|
||||||
|
raise ValueError("参数不足,算逑!")
|
||||||
|
note_count_per_instruments = {}
|
||||||
|
qualified_note_count_per_instruments = {}
|
||||||
|
for this_note in [k for j in music_channels.values() for k in j]:
|
||||||
|
if this_note.sound_name in note_count_per_instruments.keys():
|
||||||
|
note_count_per_instruments[this_note.sound_name] += 1
|
||||||
|
qualified_note_count_per_instruments[
|
||||||
|
this_note.sound_name
|
||||||
|
] += is_note_in_diapason(this_note)
|
||||||
|
else:
|
||||||
|
note_count_per_instruments[this_note.sound_name] = 1
|
||||||
|
qualified_note_count_per_instruments[this_note.sound_name] = int(
|
||||||
|
is_note_in_diapason(this_note)
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
sum(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
(
|
||||||
|
MM_INSTRUMENT_RANGE_TABLE[inst][-1]
|
||||||
|
* note_count
|
||||||
|
/ total_note_count
|
||||||
|
- MM_INSTRUMENT_RANGE_TABLE[inst][-1]
|
||||||
|
)
|
||||||
|
* (note_count - qualified_note_count_per_instruments[inst])
|
||||||
|
)
|
||||||
|
for inst, note_count in note_count_per_instruments.items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
/ total_instrument_count
|
||||||
|
/ total_note_count
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_music_note_channels(
|
def to_music_note_channels(
|
||||||
midi: mido.MidiFile,
|
midi: mido.MidiFile,
|
||||||
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
|
||||||
ignore_mismatch_error: bool = True,
|
ignore_mismatch_error: bool = True,
|
||||||
) -> NoteChannelType:
|
speed: float = 1.0,
|
||||||
|
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]]:
|
||||||
"""
|
"""
|
||||||
将midi解析并转换为频道音符字典
|
将midi解析并转换为频道音符字典
|
||||||
|
|
||||||
@ -136,14 +279,20 @@ class MusicSequence:
|
|||||||
Dict[int,List[SingleNote,]]
|
Dict[int,List[SingleNote,]]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if speed == 0:
|
||||||
|
raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。")
|
||||||
|
|
||||||
# if midi is None:
|
# if midi is None:
|
||||||
# raise MidiUnboundError(
|
# raise MidiUnboundError(
|
||||||
# "Midi参量为空。你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。"
|
# "Midi参量为空。你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。"
|
||||||
# )
|
# )
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
midi_channels: NoteChannelType = empty_midi_channels(staff=[])
|
midi_channels: MineNoteChannelType = empty_midi_channels(staff=[])
|
||||||
tempo = default_tempo_value
|
tempo = default_tempo_value
|
||||||
|
note_count = 0
|
||||||
|
note_count_per_instruments: Dict[str, int] = {}
|
||||||
|
qualified_note_count_per_instruments: Dict[str, int] = {}
|
||||||
|
|
||||||
# 我们来用通道统计音乐信息
|
# 我们来用通道统计音乐信息
|
||||||
# 但是是用分轨的思路的
|
# 但是是用分轨的思路的
|
||||||
@ -205,27 +354,47 @@ class MusicSequence:
|
|||||||
(msg.note, channel_program[msg.channel])
|
(msg.note, channel_program[msg.channel])
|
||||||
)
|
)
|
||||||
note_queue_B[msg.channel].remove((_velocity, _ms))
|
note_queue_B[msg.channel].remove((_velocity, _ms))
|
||||||
|
|
||||||
midi_channels[msg.channel].append(
|
midi_channels[msg.channel].append(
|
||||||
SingleNote(
|
that_note := midi_msgs_to_minenote(
|
||||||
instrument=msg.note,
|
inst_=(
|
||||||
pitch=channel_program[msg.channel],
|
msg.note
|
||||||
velocity=_velocity,
|
if msg.channel == 9
|
||||||
startime=_ms,
|
else channel_program[msg.channel]
|
||||||
lastime=microseconds - _ms,
|
),
|
||||||
track_number=track_no,
|
note_=(
|
||||||
is_percussion=True,
|
channel_program[msg.channel]
|
||||||
)
|
if msg.channel == 9
|
||||||
if msg.channel == 9
|
else msg.note
|
||||||
else SingleNote(
|
),
|
||||||
instrument=channel_program[msg.channel],
|
velocity_=_velocity,
|
||||||
pitch=msg.note,
|
start_time_=_ms,
|
||||||
velocity=_velocity,
|
duration_=microseconds - _ms,
|
||||||
startime=_ms,
|
track_no_=track_no,
|
||||||
lastime=microseconds - _ms,
|
percussive_=(msg.channel == 9),
|
||||||
track_number=track_no,
|
play_speed=speed,
|
||||||
is_percussion=False,
|
midi_reference_table=(
|
||||||
|
percussion_note_rtable
|
||||||
|
if msg.channel == 9
|
||||||
|
else pitched_note_rtable
|
||||||
|
),
|
||||||
|
volume_processing_method_=vol_processing_function,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
note_count += 1
|
||||||
|
if (
|
||||||
|
that_note.sound_name
|
||||||
|
in note_count_per_instruments.keys()
|
||||||
|
):
|
||||||
|
note_count_per_instruments[that_note.sound_name] += 1
|
||||||
|
qualified_note_count_per_instruments[
|
||||||
|
that_note.sound_name
|
||||||
|
] += is_note_in_diapason(that_note)
|
||||||
|
else:
|
||||||
|
note_count_per_instruments[that_note.sound_name] = 1
|
||||||
|
qualified_note_count_per_instruments[
|
||||||
|
that_note.sound_name
|
||||||
|
] = int(is_note_in_diapason(that_note))
|
||||||
else:
|
else:
|
||||||
if ignore_mismatch_error:
|
if ignore_mismatch_error:
|
||||||
print(
|
print(
|
||||||
@ -254,12 +423,17 @@ class MusicSequence:
|
|||||||
del tempo
|
del tempo
|
||||||
channels = dict(
|
channels = dict(
|
||||||
[
|
[
|
||||||
(channel_no, sorted(channel_notes, key=lambda note: note.start_time))
|
(channel_no, sorted(channel_notes, key=lambda note: note.start_tick))
|
||||||
for channel_no, channel_notes in midi_channels.items()
|
for channel_no, channel_notes in midi_channels.items()
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
return channels
|
return (
|
||||||
|
channels,
|
||||||
|
note_count,
|
||||||
|
note_count_per_instruments,
|
||||||
|
qualified_note_count_per_instruments,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MidiConvert(MusicSequence):
|
class MidiConvert(MusicSequence):
|
||||||
@ -267,42 +441,31 @@ class MidiConvert(MusicSequence):
|
|||||||
将Midi文件转换为我的世界内容
|
将Midi文件转换为我的世界内容
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pitched_note_reference_table: MidiInstrumentTableType
|
|
||||||
"""乐音乐器Midi-MC对照表"""
|
|
||||||
|
|
||||||
percussion_note_referrence_table: MidiInstrumentTableType
|
|
||||||
"""打击乐器Midi-MC对照表"""
|
|
||||||
|
|
||||||
volume_processing_function: FittingFunctionType
|
|
||||||
"""音量处理函数"""
|
|
||||||
|
|
||||||
enable_old_exe_format: bool
|
enable_old_exe_format: bool
|
||||||
"""是否启用旧版execute指令格式"""
|
"""是否启用旧版execute指令格式"""
|
||||||
|
|
||||||
execute_cmd_head: str
|
execute_cmd_head: str
|
||||||
"""execute指令头部"""
|
"""execute指令头部"""
|
||||||
|
|
||||||
music_command_list: List[SingleCommand]
|
music_command_list: List[MineCommand]
|
||||||
"""音乐指令列表"""
|
"""音乐指令列表"""
|
||||||
|
|
||||||
music_tick_num: int
|
progress_bar_command: List[MineCommand]
|
||||||
"""音乐总延迟"""
|
|
||||||
|
|
||||||
progress_bar_command: List[SingleCommand]
|
|
||||||
"""进度条指令列表"""
|
"""进度条指令列表"""
|
||||||
|
|
||||||
music_deviation: int
|
@classmethod
|
||||||
"""音乐音调总偏移"""
|
def from_mido_obj(
|
||||||
|
cls,
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
midi_obj: mido.MidiFile,
|
midi_obj: mido.MidiFile,
|
||||||
midi_name: str,
|
midi_name: str,
|
||||||
deviation: Union[int, Literal[None]] = 0,
|
|
||||||
ignore_mismatch_error: bool = True,
|
ignore_mismatch_error: bool = True,
|
||||||
enable_old_exe_format: bool = False,
|
playment_speed: float = 1,
|
||||||
|
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||||
pitched_note_rtable: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
pitched_note_rtable: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_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,
|
||||||
vol_processing_function: FittingFunctionType = natural_curve,
|
vol_processing_function: FittingFunctionType = natural_curve,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@ -322,40 +485,42 @@ class MidiConvert(MusicSequence):
|
|||||||
打击乐器Midi-MC对照表
|
打击乐器Midi-MC对照表
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(MidiConvert, self).from_mido(
|
cls.enable_old_exe_format: bool = enable_old_exe_format
|
||||||
midi_obj,
|
|
||||||
midi_name,
|
|
||||||
mismatch_error_ignorance=ignore_mismatch_error,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.music_deviation = (
|
cls.execute_cmd_head = (
|
||||||
self.guess_deviation() if deviation is None else deviation
|
|
||||||
)
|
|
||||||
|
|
||||||
self.enable_old_exe_format: bool = enable_old_exe_format
|
|
||||||
|
|
||||||
self.execute_cmd_head = (
|
|
||||||
"execute {} ~ ~ ~ "
|
"execute {} ~ ~ ~ "
|
||||||
if enable_old_exe_format
|
if enable_old_exe_format
|
||||||
else "execute as {} at @s positioned ~ ~ ~ run "
|
else "execute as {} at @s positioned ~ ~ ~ run "
|
||||||
)
|
)
|
||||||
|
|
||||||
self.pitched_note_reference_table = pitched_note_rtable
|
cls.progress_bar_command = cls.music_command_list = []
|
||||||
self.percussion_note_referrence_table = percussion_note_rtable
|
cls.channels = {}
|
||||||
self.volume_processing_function = vol_processing_function
|
|
||||||
|
|
||||||
self.progress_bar_command = self.music_command_list = []
|
return cls.from_mido(
|
||||||
self.channels = {}
|
mido_file=midi_obj,
|
||||||
self.music_tick_num = 0
|
midi_music_name=midi_name,
|
||||||
|
speed_multiplier=playment_speed,
|
||||||
|
pitched_note_referance_table=pitched_note_rtable,
|
||||||
|
percussion_note_referance_table=percussion_note_rtable,
|
||||||
|
minium_vol=minium_volume,
|
||||||
|
volume_processing_function=vol_processing_function,
|
||||||
|
default_tempo=default_tempo_value,
|
||||||
|
devation_guess_enabled=enable_devation_guess,
|
||||||
|
mismatch_error_ignorance=ignore_mismatch_error,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_midi_file(
|
def from_midi_file(
|
||||||
cls,
|
cls,
|
||||||
midi_file_path: str,
|
midi_file_path: str,
|
||||||
ignore_mismatch_error: bool = True,
|
mismatch_error_ignorance: bool = True,
|
||||||
old_exe_format: bool = False,
|
play_speed: float = 1,
|
||||||
|
default_tempo: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||||
pitched_note_table: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
pitched_note_table: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
percussion_note_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_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,
|
vol_processing_func: FittingFunctionType = natural_curve,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@ -363,14 +528,19 @@ class MidiConvert(MusicSequence):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
midi_file: str
|
midi_file_path: str
|
||||||
midi文件地址
|
midi文件地址
|
||||||
enable_old_exe_format: bool
|
|
||||||
是否启用旧版(≤1.19)指令格式,默认为否
|
speed: float
|
||||||
|
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||||
pitched_note_table: Dict[int, Tuple[str, int]]
|
pitched_note_table: Dict[int, Tuple[str, int]]
|
||||||
乐音乐器Midi-MC对照表
|
乐音乐器Midi-MC对照表
|
||||||
percussion_note_table: Dict[int, Tuple[str, int]]
|
percussion_note_table: Dict[int, Tuple[str, int]]
|
||||||
打击乐器Midi-MC对照表
|
打击乐器Midi-MC对照表
|
||||||
|
enable_old_exe_format: bool
|
||||||
|
是否启用旧版(≤1.19)指令格式,默认为否
|
||||||
|
min_volume: float
|
||||||
|
最小播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值
|
||||||
"""
|
"""
|
||||||
|
|
||||||
midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0].replace(
|
midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0].replace(
|
||||||
@ -379,18 +549,21 @@ class MidiConvert(MusicSequence):
|
|||||||
"""文件名,不含路径且不含后缀"""
|
"""文件名,不含路径且不含后缀"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return cls(
|
return cls.from_mido_obj(
|
||||||
mido.MidiFile(
|
midi_obj=mido.MidiFile(
|
||||||
midi_file_path,
|
midi_file_path,
|
||||||
clip=True,
|
clip=True,
|
||||||
),
|
),
|
||||||
midi_music_name,
|
midi_name=midi_music_name,
|
||||||
None,
|
ignore_mismatch_error=mismatch_error_ignorance,
|
||||||
old_exe_format,
|
playment_speed=play_speed,
|
||||||
ignore_mismatch_error,
|
default_tempo_value=default_tempo,
|
||||||
pitched_note_table,
|
pitched_note_rtable=pitched_note_table,
|
||||||
percussion_note_table,
|
percussion_note_rtable=percussion_note_table,
|
||||||
vol_processing_func,
|
enable_devation_guess=devation_guess_enabled,
|
||||||
|
enable_old_exe_format=old_exe_format,
|
||||||
|
minium_volume=min_volume,
|
||||||
|
vol_processing_function=vol_processing_func,
|
||||||
)
|
)
|
||||||
except (ValueError, TypeError) as E:
|
except (ValueError, TypeError) as E:
|
||||||
raise MidiDestroyedError(f"文件{midi_file_path}损坏:{E}")
|
raise MidiDestroyedError(f"文件{midi_file_path}损坏:{E}")
|
||||||
@ -400,17 +573,12 @@ class MidiConvert(MusicSequence):
|
|||||||
# ……真的那么重要吗
|
# ……真的那么重要吗
|
||||||
# 我又几曾何时,知道祂真的会抛下我
|
# 我又几曾何时,知道祂真的会抛下我
|
||||||
|
|
||||||
def guess_deviation(self) -> int:
|
|
||||||
|
|
||||||
# 等我想想看
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def form_progress_bar(
|
def form_progress_bar(
|
||||||
self,
|
self,
|
||||||
max_score: int,
|
max_score: int,
|
||||||
scoreboard_name: str,
|
scoreboard_name: str,
|
||||||
progressbar_style: ProgressBarStyle = DEFAULT_PROGRESSBAR_STYLE,
|
progressbar_style: ProgressBarStyle = DEFAULT_PROGRESSBAR_STYLE,
|
||||||
) -> List[SingleCommand]:
|
) -> List[MineCommand]:
|
||||||
"""
|
"""
|
||||||
生成进度条
|
生成进度条
|
||||||
|
|
||||||
@ -446,7 +614,7 @@ class MidiConvert(MusicSequence):
|
|||||||
perEach = max_score / pgs_style.count("_")
|
perEach = max_score / pgs_style.count("_")
|
||||||
"""每个进度条代表的分值"""
|
"""每个进度条代表的分值"""
|
||||||
|
|
||||||
result: List[SingleCommand] = []
|
result: List[MineCommand] = []
|
||||||
|
|
||||||
if r"%^s" in pgs_style:
|
if r"%^s" in pgs_style:
|
||||||
pgs_style = pgs_style.replace(r"%^s", str(max_score))
|
pgs_style = pgs_style.replace(r"%^s", str(max_score))
|
||||||
@ -457,7 +625,7 @@ class MidiConvert(MusicSequence):
|
|||||||
sbn_pc = scoreboard_name[:2]
|
sbn_pc = scoreboard_name[:2]
|
||||||
if r"%%%" in pgs_style:
|
if r"%%%" in pgs_style:
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
'scoreboard objectives add {}PercT dummy "百分比计算"'.format(
|
'scoreboard objectives add {}PercT dummy "百分比计算"'.format(
|
||||||
sbn_pc
|
sbn_pc
|
||||||
),
|
),
|
||||||
@ -465,7 +633,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -476,7 +644,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -485,7 +653,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -496,7 +664,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -507,7 +675,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -524,7 +692,7 @@ class MidiConvert(MusicSequence):
|
|||||||
|
|
||||||
if r"%%t" in pgs_style:
|
if r"%%t" in pgs_style:
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format(
|
'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format(
|
||||||
sbn_pc
|
sbn_pc
|
||||||
),
|
),
|
||||||
@ -532,7 +700,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(
|
'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(
|
||||||
sbn_pc
|
sbn_pc
|
||||||
),
|
),
|
||||||
@ -540,7 +708,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -549,7 +717,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -559,7 +727,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
|
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -570,7 +738,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -581,7 +749,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -593,7 +761,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
|
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -605,7 +773,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
|
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores={" + scoreboard_name + "=1..}]"
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
)
|
)
|
||||||
@ -642,7 +810,7 @@ class MidiConvert(MusicSequence):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
r"@a[scores={"
|
r"@a[scores={"
|
||||||
+ scoreboard_name
|
+ scoreboard_name
|
||||||
@ -658,20 +826,20 @@ class MidiConvert(MusicSequence):
|
|||||||
|
|
||||||
if r"%%%" in pgs_style:
|
if r"%%%" in pgs_style:
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
"scoreboard objectives remove {}PercT".format(sbn_pc),
|
"scoreboard objectives remove {}PercT".format(sbn_pc),
|
||||||
annotation="移除临时百分比变量",
|
annotation="移除临时百分比变量",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if r"%%t" in pgs_style:
|
if r"%%t" in pgs_style:
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
"scoreboard objectives remove {}TMinT".format(sbn_pc),
|
"scoreboard objectives remove {}TMinT".format(sbn_pc),
|
||||||
annotation="移除临时分变量",
|
annotation="移除临时分变量",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.append(
|
result.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
"scoreboard objectives remove {}TSecT".format(sbn_pc),
|
"scoreboard objectives remove {}TSecT".format(sbn_pc),
|
||||||
annotation="移除临时秒变量",
|
annotation="移除临时秒变量",
|
||||||
)
|
)
|
||||||
@ -683,9 +851,7 @@ class MidiConvert(MusicSequence):
|
|||||||
def to_command_list_in_score(
|
def to_command_list_in_score(
|
||||||
self,
|
self,
|
||||||
scoreboard_name: str = "mscplay",
|
scoreboard_name: str = "mscplay",
|
||||||
max_volume: float = 1.0,
|
) -> Tuple[List[List[MineCommand]], int, int]:
|
||||||
speed: float = 1.0,
|
|
||||||
) -> Tuple[List[List[SingleCommand]], int, int]:
|
|
||||||
"""
|
"""
|
||||||
将midi转换为我的世界命令列表
|
将midi转换为我的世界命令列表
|
||||||
|
|
||||||
@ -693,20 +859,12 @@ class MidiConvert(MusicSequence):
|
|||||||
----------
|
----------
|
||||||
scoreboard_name: str
|
scoreboard_name: str
|
||||||
我的世界的计分板名称
|
我的世界的计分板名称
|
||||||
max_volume: float
|
|
||||||
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放
|
|
||||||
speed: float
|
|
||||||
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
|
tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if speed == 0:
|
|
||||||
raise ZeroSpeedError("播放速度仅可为(0,1]范围内的正实数")
|
|
||||||
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
|
||||||
|
|
||||||
command_channels = []
|
command_channels = []
|
||||||
command_amount = 0
|
command_amount = 0
|
||||||
max_score = 0
|
max_score = 0
|
||||||
@ -720,59 +878,44 @@ class MidiConvert(MusicSequence):
|
|||||||
this_channel = []
|
this_channel = []
|
||||||
|
|
||||||
for note in channel:
|
for note in channel:
|
||||||
score_now = round(note.start_time / float(speed) / 50)
|
max_score = max(max_score, note.start_tick)
|
||||||
max_score = max(max_score, score_now)
|
|
||||||
|
|
||||||
(
|
(
|
||||||
mc_sound_ID,
|
mc_sound_ID,
|
||||||
mc_distance_volume,
|
relative_coordinates,
|
||||||
volume_percentage,
|
volume_percentage,
|
||||||
mc_pitch,
|
mc_pitch,
|
||||||
) = note_to_command_parameters(
|
) = minenote_to_command_paramaters(
|
||||||
note,
|
note,
|
||||||
(
|
pitch_deviation=self.music_deviation,
|
||||||
self.percussion_note_referrence_table
|
|
||||||
if note.percussive
|
|
||||||
else self.pitched_note_reference_table
|
|
||||||
),
|
|
||||||
deviation=self.music_deviation,
|
|
||||||
volume_percentage=(
|
|
||||||
(max_volume) if note.track_no == 0 else (max_volume * 0.9)
|
|
||||||
),
|
|
||||||
volume_processing_method=self.volume_processing_function,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
this_channel.append(
|
this_channel.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
(
|
(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores=({}={})]".format(scoreboard_name, score_now)
|
"@a[scores=({}={})]".format(
|
||||||
|
scoreboard_name, note.start_tick
|
||||||
|
)
|
||||||
.replace("(", r"{")
|
.replace("(", r"{")
|
||||||
.replace(")", r"}")
|
.replace(")", r"}")
|
||||||
)
|
)
|
||||||
+ (
|
+ r"playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
||||||
r"playsound {} @s ^ ^ ^{} {}".format(
|
mc_sound_ID,
|
||||||
mc_sound_ID, mc_distance_volume, volume_percentage
|
*relative_coordinates,
|
||||||
)
|
volume_percentage,
|
||||||
if note.percussive
|
1.0 if note.percussive else mc_pitch,
|
||||||
else r"playsound {} @s ^ ^ ^{} {} {}".format(
|
self.minium_volume,
|
||||||
mc_sound_ID,
|
|
||||||
mc_distance_volume,
|
|
||||||
volume_percentage,
|
|
||||||
mc_pitch,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
annotation=(
|
annotation=(
|
||||||
"在{}播放{}%的{}噪音".format(
|
"在{}播放噪音{}".format(
|
||||||
mctick2timestr(score_now),
|
mctick2timestr(note.start_tick),
|
||||||
max_volume * 100,
|
|
||||||
mc_sound_ID,
|
mc_sound_ID,
|
||||||
)
|
)
|
||||||
if note.percussive
|
if note.percussive
|
||||||
else "在{}播放{}%的{}乐音".format(
|
else "在{}播放乐音{}".format(
|
||||||
mctick2timestr(score_now),
|
mctick2timestr(note.start_tick),
|
||||||
max_volume * 100,
|
|
||||||
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -785,24 +928,17 @@ class MidiConvert(MusicSequence):
|
|||||||
self.music_command_list.extend(this_channel)
|
self.music_command_list.extend(this_channel)
|
||||||
command_channels.append(this_channel)
|
command_channels.append(this_channel)
|
||||||
|
|
||||||
self.music_tick_num = max_score
|
|
||||||
return command_channels, command_amount, max_score
|
return command_channels, command_amount, max_score
|
||||||
|
|
||||||
def to_command_list_in_delay(
|
def to_command_list_in_delay(
|
||||||
self,
|
self,
|
||||||
max_volume: float = 1.0,
|
|
||||||
speed: float = 1.0,
|
|
||||||
player_selector: str = "@a",
|
player_selector: str = "@a",
|
||||||
) -> Tuple[List[SingleCommand], int, int]:
|
) -> Tuple[List[MineCommand], int, int]:
|
||||||
"""
|
"""
|
||||||
将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
max_volume: float
|
|
||||||
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
|
||||||
speed: float
|
|
||||||
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
|
||||||
player_selector: str
|
player_selector: str
|
||||||
玩家选择器,默认为`@a`
|
玩家选择器,默认为`@a`
|
||||||
|
|
||||||
@ -811,25 +947,18 @@ class MidiConvert(MusicSequence):
|
|||||||
tuple( list[SingleCommand,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
tuple( list[SingleCommand,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if speed == 0:
|
notes_list: List[MineNote] = sorted(
|
||||||
raise ZeroSpeedError("播放速度仅可为(0,1]范围内的正实数")
|
[i for j in self.channels.values() for i in j],
|
||||||
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
key=lambda note: note.start_tick,
|
||||||
|
)
|
||||||
notes_list: List[SingleNote] = []
|
|
||||||
|
|
||||||
# 此处 我们把通道视为音轨
|
# 此处 我们把通道视为音轨
|
||||||
for channel in self.channels.values():
|
|
||||||
notes_list.extend(channel)
|
|
||||||
|
|
||||||
notes_list.sort(key=lambda a: a.start_time)
|
|
||||||
|
|
||||||
self.music_command_list = []
|
self.music_command_list = []
|
||||||
multi = max_multi = 0
|
multi = max_multi = 0
|
||||||
delaytime_previous = 0
|
delaytime_previous = 0
|
||||||
|
|
||||||
for note in notes_list:
|
for note in notes_list:
|
||||||
delaytime_now = round(note.start_time / speed / 50)
|
if (tickdelay := (note.start_tick - delaytime_previous)) == 0:
|
||||||
if (tickdelay := (delaytime_now - delaytime_previous)) == 0:
|
|
||||||
multi += 1
|
multi += 1
|
||||||
else:
|
else:
|
||||||
max_multi = max(max_multi, multi)
|
max_multi = max(max_multi, multi)
|
||||||
@ -837,68 +966,53 @@ class MidiConvert(MusicSequence):
|
|||||||
|
|
||||||
(
|
(
|
||||||
mc_sound_ID,
|
mc_sound_ID,
|
||||||
mc_distance_volume,
|
relative_coordinates,
|
||||||
volume_percentage,
|
volume_percentage,
|
||||||
mc_pitch,
|
mc_pitch,
|
||||||
) = note_to_command_parameters(
|
) = minenote_to_command_paramaters(
|
||||||
note,
|
note,
|
||||||
(
|
pitch_deviation=self.music_deviation,
|
||||||
self.percussion_note_referrence_table
|
|
||||||
if note.percussive
|
|
||||||
else self.pitched_note_reference_table
|
|
||||||
),
|
|
||||||
deviation=self.music_deviation,
|
|
||||||
volume_percentage=(
|
|
||||||
(max_volume) if note.track_no == 0 else (max_volume * 0.9)
|
|
||||||
),
|
|
||||||
volume_processing_method=self.volume_processing_function,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.music_command_list.append(
|
self.music_command_list.append(
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
command=(
|
command=(
|
||||||
self.execute_cmd_head.format(player_selector)
|
self.execute_cmd_head.format(player_selector)
|
||||||
+ (
|
+ r"playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
||||||
r"playsound {} @s ^ ^ ^{} {}".format(
|
mc_sound_ID,
|
||||||
mc_sound_ID, mc_distance_volume, volume_percentage
|
*relative_coordinates,
|
||||||
)
|
volume_percentage,
|
||||||
if note.percussive
|
1.0 if note.percussive else mc_pitch,
|
||||||
else r"playsound {} @s ^ ^ ^{} {} {}".format(
|
self.minium_volume,
|
||||||
mc_sound_ID,
|
|
||||||
mc_distance_volume,
|
|
||||||
volume_percentage,
|
|
||||||
mc_pitch,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
annotation=(
|
annotation=(
|
||||||
"在{}播放{}%的{}噪音".format(
|
"在{}播放噪音{}".format(
|
||||||
mctick2timestr(delaytime_now),
|
mctick2timestr(note.start_tick),
|
||||||
max_volume * 100,
|
|
||||||
mc_sound_ID,
|
mc_sound_ID,
|
||||||
)
|
)
|
||||||
if note.percussive
|
if note.percussive
|
||||||
else "在{}播放{}%的{}乐音".format(
|
else "在{}播放乐音{}".format(
|
||||||
mctick2timestr(delaytime_now),
|
mctick2timestr(note.start_tick),
|
||||||
max_volume * 100,
|
|
||||||
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tick_delay=tickdelay,
|
tick_delay=tickdelay,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
delaytime_previous = delaytime_now
|
delaytime_previous = note.start_tick
|
||||||
|
|
||||||
self.music_tick_num = round(notes_list[-1].start_time / speed / 50)
|
return self.music_command_list, notes_list[-1].start_tick, max_multi + 1
|
||||||
return self.music_command_list, self.music_tick_num, max_multi + 1
|
|
||||||
|
|
||||||
def copy_important(self):
|
def copy_important(self):
|
||||||
dst = MidiConvert(
|
dst = MidiConvert.from_mido_obj(
|
||||||
midi_obj=mido.MidiFile(),
|
midi_obj=mido.MidiFile(),
|
||||||
midi_name=self.music_name,
|
midi_name=self.music_name,
|
||||||
enable_old_exe_format=self.enable_old_exe_format,
|
enable_old_exe_format=self.enable_old_exe_format,
|
||||||
|
pitched_note_rtable={},
|
||||||
|
percussion_note_rtable={},
|
||||||
|
vol_processing_function=lambda a: a,
|
||||||
)
|
)
|
||||||
dst.music_command_list = [i.copy() for i in self.music_command_list]
|
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]
|
dst.progress_bar_command = [i.copy() for i in self.progress_bar_command]
|
||||||
dst.music_tick_num = self.music_tick_num
|
|
||||||
return dst
|
return dst
|
||||||
|
@ -56,7 +56,7 @@ def to_addon_pack_in_score(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_score(
|
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_score(
|
||||||
scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier
|
scoreboard_name=scoreboard_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目,然后创建
|
# 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目,然后创建
|
||||||
@ -180,9 +180,7 @@ def to_addon_pack_in_delay(
|
|||||||
)
|
)
|
||||||
|
|
||||||
command_list, max_delay = midi_cvt.to_command_list_in_delay(
|
command_list, max_delay = midi_cvt.to_command_list_in_delay(
|
||||||
data_cfg.volume_ratio,
|
player_selector=player,
|
||||||
data_cfg.speed_multiplier,
|
|
||||||
player,
|
|
||||||
)[:2]
|
)[:2]
|
||||||
|
|
||||||
if not os.path.exists(data_cfg.dist_path):
|
if not os.path.exists(data_cfg.dist_path):
|
||||||
@ -377,9 +375,7 @@ def to_addon_pack_in_repeater(
|
|||||||
)
|
)
|
||||||
|
|
||||||
command_list, max_delay, max_together = midi_cvt.to_command_list_in_delay(
|
command_list, max_delay, max_together = midi_cvt.to_command_list_in_delay(
|
||||||
data_cfg.volume_ratio,
|
player_selector=player,
|
||||||
data_cfg.speed_multiplier,
|
|
||||||
player,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(data_cfg.dist_path):
|
if not os.path.exists(data_cfg.dist_path):
|
||||||
|
@ -18,7 +18,7 @@ Terms & Conditions: License.md in the root directory
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from ..constants import x, y, z
|
from ..constants import x, y, z
|
||||||
from ..subclass import SingleCommand
|
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
|
||||||
|
|
||||||
BDX_MOVE_KEY = {
|
BDX_MOVE_KEY = {
|
||||||
@ -123,7 +123,7 @@ def form_command_block_in_BDX_bytes(
|
|||||||
|
|
||||||
|
|
||||||
def commands_to_BDX_bytes(
|
def commands_to_BDX_bytes(
|
||||||
commands_list: List[SingleCommand],
|
commands_list: List[MineCommand],
|
||||||
max_height: int = 64,
|
max_height: int = 64,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
@ -17,7 +17,7 @@ import os
|
|||||||
import brotli
|
import brotli
|
||||||
|
|
||||||
from ...main import MidiConvert
|
from ...main import MidiConvert
|
||||||
from ...subclass import SingleCommand
|
from ...subclass import MineCommand
|
||||||
from ..bdx import (
|
from ..bdx import (
|
||||||
bdx_move,
|
bdx_move,
|
||||||
commands_to_BDX_bytes,
|
commands_to_BDX_bytes,
|
||||||
@ -61,16 +61,14 @@ def to_BDX_file_in_score(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
cmdlist, command_count, max_score = midi_cvt.to_command_list_in_score(
|
cmdlist, command_count, max_score = midi_cvt.to_command_list_in_score(
|
||||||
scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier
|
scoreboard_name=scoreboard_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(data_cfg.dist_path):
|
if not os.path.exists(data_cfg.dist_path):
|
||||||
os.makedirs(data_cfg.dist_path)
|
os.makedirs(data_cfg.dist_path)
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.abspath(
|
os.path.abspath(os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")),
|
||||||
os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")
|
|
||||||
),
|
|
||||||
"w+",
|
"w+",
|
||||||
) as f:
|
) as f:
|
||||||
f.write("BD@")
|
f.write("BD@")
|
||||||
@ -83,7 +81,7 @@ def to_BDX_file_in_score(
|
|||||||
midi_cvt.music_command_list
|
midi_cvt.music_command_list
|
||||||
+ (
|
+ (
|
||||||
[
|
[
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
command="scoreboard players reset @a[scores={"
|
command="scoreboard players reset @a[scores={"
|
||||||
+ scoreboard_name
|
+ scoreboard_name
|
||||||
+ "="
|
+ "="
|
||||||
@ -118,9 +116,7 @@ def to_BDX_file_in_score(
|
|||||||
_bytes += cmdBytes
|
_bytes += cmdBytes
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.abspath(
|
os.path.abspath(os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")),
|
||||||
os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")
|
|
||||||
),
|
|
||||||
"ab+",
|
"ab+",
|
||||||
) as f:
|
) as f:
|
||||||
f.write(brotli.compress(_bytes + b"XE"))
|
f.write(brotli.compress(_bytes + b"XE"))
|
||||||
@ -157,18 +153,14 @@ def to_BDX_file_in_delay(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
cmdlist, max_delay = midi_cvt.to_command_list_in_delay(
|
cmdlist, max_delay = midi_cvt.to_command_list_in_delay(
|
||||||
data_cfg.volume_ratio,
|
player_selector=player,
|
||||||
data_cfg.speed_multiplier,
|
|
||||||
player,
|
|
||||||
)[:2]
|
)[:2]
|
||||||
|
|
||||||
if not os.path.exists(data_cfg.dist_path):
|
if not os.path.exists(data_cfg.dist_path):
|
||||||
os.makedirs(data_cfg.dist_path)
|
os.makedirs(data_cfg.dist_path)
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.abspath(
|
os.path.abspath(os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")),
|
||||||
os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")
|
|
||||||
),
|
|
||||||
"w+",
|
"w+",
|
||||||
) as f:
|
) as f:
|
||||||
f.write("BD@")
|
f.write("BD@")
|
||||||
@ -216,9 +208,7 @@ def to_BDX_file_in_delay(
|
|||||||
_bytes += cmdBytes
|
_bytes += cmdBytes
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.abspath(
|
os.path.abspath(os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")),
|
||||||
os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")
|
|
||||||
),
|
|
||||||
"ab+",
|
"ab+",
|
||||||
) as f:
|
) as f:
|
||||||
f.write(brotli.compress(_bytes + b"XE"))
|
f.write(brotli.compress(_bytes + b"XE"))
|
||||||
|
@ -23,17 +23,11 @@ from ..subclass import DEFAULT_PROGRESSBAR_STYLE, ProgressBarStyle
|
|||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class ConvertConfig:
|
class ConvertConfig: # 必定要改
|
||||||
"""
|
"""
|
||||||
转换通用设置存储类
|
转换通用设置存储类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
volume_ratio: float
|
|
||||||
"""音量比例"""
|
|
||||||
|
|
||||||
speed_multiplier: float
|
|
||||||
"""速度倍率"""
|
|
||||||
|
|
||||||
progressbar_style: Union[ProgressBarStyle, None]
|
progressbar_style: Union[ProgressBarStyle, None]
|
||||||
"""进度条样式"""
|
"""进度条样式"""
|
||||||
|
|
||||||
@ -43,8 +37,6 @@ class ConvertConfig:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
output_path: str,
|
output_path: str,
|
||||||
volume: float = 1.0,
|
|
||||||
speed: float = 1.0,
|
|
||||||
progressbar: Union[bool, Tuple[str, Tuple[str, str]], ProgressBarStyle] = True,
|
progressbar: Union[bool, Tuple[str, Tuple[str, str]], ProgressBarStyle] = True,
|
||||||
ignore_progressbar_param_error: bool = False,
|
ignore_progressbar_param_error: bool = False,
|
||||||
):
|
):
|
||||||
@ -67,12 +59,6 @@ class ConvertConfig:
|
|||||||
self.dist_path = output_path
|
self.dist_path = output_path
|
||||||
"""输出目录"""
|
"""输出目录"""
|
||||||
|
|
||||||
self.volume_ratio = volume
|
|
||||||
"""音量比例"""
|
|
||||||
|
|
||||||
self.speed_multiplier = speed
|
|
||||||
"""速度倍率"""
|
|
||||||
|
|
||||||
if progressbar:
|
if progressbar:
|
||||||
# 此处是对于仅有 True 的参数和自定义参数的判断
|
# 此处是对于仅有 True 的参数和自定义参数的判断
|
||||||
# 改这一段没🐎
|
# 改这一段没🐎
|
||||||
|
@ -21,4 +21,8 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
__author__ = (("金羿", "Eilles Wan"),)
|
__author__ = (("金羿", "Eilles Wan"),)
|
||||||
|
|
||||||
from .main import to_mcstructure_file_in_delay, to_mcstructure_file_in_repeater, to_mcstructure_file_in_score
|
from .main import (
|
||||||
|
to_mcstructure_file_in_delay,
|
||||||
|
to_mcstructure_file_in_repeater,
|
||||||
|
to_mcstructure_file_in_score,
|
||||||
|
)
|
||||||
|
@ -17,7 +17,7 @@ from typing import Literal
|
|||||||
# from ...exceptions import CommandFormatError
|
# from ...exceptions import CommandFormatError
|
||||||
from ...main import MidiConvert
|
from ...main import MidiConvert
|
||||||
from ..main import ConvertConfig
|
from ..main import ConvertConfig
|
||||||
from ...subclass import SingleCommand
|
from ...subclass import MineCommand
|
||||||
from ..mcstructure import (
|
from ..mcstructure import (
|
||||||
COMPABILITY_VERSION_117,
|
COMPABILITY_VERSION_117,
|
||||||
COMPABILITY_VERSION_119,
|
COMPABILITY_VERSION_119,
|
||||||
@ -58,9 +58,7 @@ def to_mcstructure_file_in_delay(
|
|||||||
)
|
)
|
||||||
|
|
||||||
cmd_list, max_delay = midi_cvt.to_command_list_in_delay(
|
cmd_list, max_delay = midi_cvt.to_command_list_in_delay(
|
||||||
data_cfg.volume_ratio,
|
player_selector=player,
|
||||||
data_cfg.speed_multiplier,
|
|
||||||
player,
|
|
||||||
)[:2]
|
)[:2]
|
||||||
|
|
||||||
if not os.path.exists(data_cfg.dist_path):
|
if not os.path.exists(data_cfg.dist_path):
|
||||||
@ -116,18 +114,17 @@ def to_mcstructure_file_in_score(
|
|||||||
)
|
)
|
||||||
|
|
||||||
cmd_list, cmd_count, max_delay = midi_cvt.to_command_list_in_score(
|
cmd_list, cmd_count, max_delay = midi_cvt.to_command_list_in_score(
|
||||||
scoreboard_name,
|
scoreboard_name=scoreboard_name,
|
||||||
data_cfg.volume_ratio,
|
|
||||||
data_cfg.speed_multiplier,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(data_cfg.dist_path):
|
if not os.path.exists(data_cfg.dist_path):
|
||||||
os.makedirs(data_cfg.dist_path)
|
os.makedirs(data_cfg.dist_path)
|
||||||
|
|
||||||
struct, size, end_pos = commands_to_structure(
|
struct, size, end_pos = commands_to_structure(
|
||||||
midi_cvt.music_command_list+(
|
midi_cvt.music_command_list
|
||||||
|
+ (
|
||||||
[
|
[
|
||||||
SingleCommand(
|
MineCommand(
|
||||||
command="scoreboard players reset @a[scores={"
|
command="scoreboard players reset @a[scores={"
|
||||||
+ scoreboard_name
|
+ scoreboard_name
|
||||||
+ "="
|
+ "="
|
||||||
@ -139,7 +136,9 @@ def to_mcstructure_file_in_score(
|
|||||||
]
|
]
|
||||||
if auto_reset
|
if auto_reset
|
||||||
else []
|
else []
|
||||||
), max_height - 1, compability_version_=compability_ver
|
),
|
||||||
|
max_height - 1,
|
||||||
|
compability_version_=compability_ver,
|
||||||
)
|
)
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
@ -188,9 +187,7 @@ def to_mcstructure_file_in_repeater(
|
|||||||
)
|
)
|
||||||
|
|
||||||
cmd_list, max_delay, max_multiple_cmd = midi_cvt.to_command_list_in_delay(
|
cmd_list, max_delay, max_multiple_cmd = midi_cvt.to_command_list_in_delay(
|
||||||
data_cfg.volume_ratio,
|
player_selector=player,
|
||||||
data_cfg.speed_multiplier,
|
|
||||||
player,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(data_cfg.dist_path):
|
if not os.path.exists(data_cfg.dist_path):
|
||||||
|
@ -21,7 +21,7 @@ from typing import List, Literal, Tuple
|
|||||||
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
|
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
|
||||||
|
|
||||||
from ..constants import x, y, z
|
from ..constants import x, y, z
|
||||||
from ..subclass import SingleCommand
|
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
|
||||||
|
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ def form_command_block_in_NBT_struct(
|
|||||||
|
|
||||||
|
|
||||||
def commands_to_structure(
|
def commands_to_structure(
|
||||||
commands: List[SingleCommand],
|
commands: List[MineCommand],
|
||||||
max_height: int = 64,
|
max_height: int = 64,
|
||||||
compability_version_: int = COMPABILITY_VERSION_119,
|
compability_version_: int = COMPABILITY_VERSION_119,
|
||||||
):
|
):
|
||||||
@ -313,7 +313,7 @@ def commands_to_structure(
|
|||||||
|
|
||||||
|
|
||||||
def commands_to_redstone_delay_structure(
|
def commands_to_redstone_delay_structure(
|
||||||
commands: List[SingleCommand],
|
commands: List[MineCommand],
|
||||||
delay_length: int,
|
delay_length: int,
|
||||||
max_multicmd_length: int,
|
max_multicmd_length: int,
|
||||||
base_block: str = "concrete",
|
base_block: str = "concrete",
|
||||||
|
@ -18,7 +18,7 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
from ..exceptions import NotDefineProgramError, ZeroSpeedError
|
from ..exceptions import NotDefineProgramError, ZeroSpeedError
|
||||||
from ..main import MidiConvert
|
from ..main import MidiConvert
|
||||||
from ..subclass import SingleCommand
|
from ..subclass import MineCommand
|
||||||
from ..utils import inst_to_sould_with_deviation, perc_inst_to_soundID_withX
|
from ..utils import inst_to_sould_with_deviation, perc_inst_to_soundID_withX
|
||||||
|
|
||||||
# 你以为写完了吗?其实并没有
|
# 你以为写完了吗?其实并没有
|
||||||
|
@ -15,4 +15,4 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
import nbtschematic
|
# import nbtschematic
|
||||||
|
@ -13,17 +13,17 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
import fcwslib
|
import fcwslib
|
||||||
|
|
||||||
from ...main import MidiConvert
|
# from ...main import MidiConvert
|
||||||
|
|
||||||
from ..main import ConvertConfig
|
# from ..main import ConvertConfig
|
||||||
from ...subclass import SingleCommand
|
# from ...subclass import MineCommand
|
||||||
|
|
||||||
|
|
||||||
def open_websocket_server(
|
# def open_websocket_server(
|
||||||
midi_cvt: MidiConvert,
|
# midi_cvt: MidiConvert,
|
||||||
data_cfg: ConvertConfig,
|
# data_cfg: ConvertConfig,
|
||||||
player: str = "@a",
|
# player: str = "@a",
|
||||||
server_dist: str = "localhost",
|
# server_dist: str = "localhost",
|
||||||
server_port: int = 8000,
|
# server_port: int = 8000,
|
||||||
):
|
# ):
|
||||||
wssever = fcwslib.Server(server=server_dist,port=server_port,debug_mode=False)
|
# wssever = fcwslib.Server(server=server_dist,port=server_port,debug_mode=False)
|
@ -18,12 +18,207 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from .types import Optional, Any, List, Mapping, Tuple, Union
|
||||||
from .types import Optional, Any, List, Mapping
|
|
||||||
|
|
||||||
from .constants import MC_PERCUSSION_INSTRUMENT_LIST
|
from .constants import MC_PERCUSSION_INSTRUMENT_LIST
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(init=False)
|
||||||
|
class MineNote:
|
||||||
|
"""存储单个音符的类"""
|
||||||
|
|
||||||
|
sound_name: str
|
||||||
|
"""乐器ID"""
|
||||||
|
|
||||||
|
note_pitch: int
|
||||||
|
"""midi音高"""
|
||||||
|
|
||||||
|
velocity: int
|
||||||
|
"""响度(力度)"""
|
||||||
|
|
||||||
|
start_tick: int
|
||||||
|
"""开始之时 命令刻"""
|
||||||
|
|
||||||
|
duration: int
|
||||||
|
"""音符持续时间 命令刻"""
|
||||||
|
|
||||||
|
track_no: int
|
||||||
|
"""音符所处的音轨"""
|
||||||
|
|
||||||
|
percussive: bool
|
||||||
|
"""是否作为打击乐器启用"""
|
||||||
|
|
||||||
|
position_displacement: Tuple[float, float, float]
|
||||||
|
"""声像位移"""
|
||||||
|
|
||||||
|
extra_info: Any
|
||||||
|
"""你觉得放什么好?"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
mc_sound_name: str,
|
||||||
|
midi_pitch: Optional[int],
|
||||||
|
midi_velocity: int,
|
||||||
|
start_time: int,
|
||||||
|
last_time: int,
|
||||||
|
track_number: int = 0,
|
||||||
|
is_percussion: Optional[bool] = None,
|
||||||
|
displacement: Optional[Tuple[float, float, float]] = None,
|
||||||
|
extra_information: Optional[Any] = None,
|
||||||
|
):
|
||||||
|
"""用于存储单个音符的类
|
||||||
|
:param mc_sound_name:`str` 《我的世界》声音ID
|
||||||
|
:param midi_pitch:`int` midi音高
|
||||||
|
:param midi_velocity:`int` midi响度(力度)
|
||||||
|
:param start_time:`int` 开始之时(命令刻)
|
||||||
|
注:此处的时间是用从乐曲开始到当前的毫秒数
|
||||||
|
:param last_time:`int` 音符延续时间(命令刻)
|
||||||
|
:param track_number:`int` 音轨编号
|
||||||
|
:param is_percussion:`bool` 是否作为打击乐器
|
||||||
|
:param displacement:`tuple[int,int,int]` 声像位移
|
||||||
|
:param extra_information:`Any` 附加信息"""
|
||||||
|
self.sound_name: str = mc_sound_name
|
||||||
|
"""乐器ID"""
|
||||||
|
self.note_pitch: int = 66 if midi_pitch is None else midi_pitch
|
||||||
|
"""midi音高"""
|
||||||
|
self.velocity: int = midi_velocity
|
||||||
|
"""响度(力度)"""
|
||||||
|
self.start_tick: int = start_time
|
||||||
|
"""开始之时 tick"""
|
||||||
|
self.duration: int = last_time
|
||||||
|
"""音符持续时间 tick"""
|
||||||
|
self.track_no: int = track_number
|
||||||
|
"""音符所处的音轨"""
|
||||||
|
|
||||||
|
self.percussive = (
|
||||||
|
(mc_sound_name in MC_PERCUSSION_INSTRUMENT_LIST)
|
||||||
|
if (is_percussion is None)
|
||||||
|
else is_percussion
|
||||||
|
)
|
||||||
|
"""是否为打击乐器"""
|
||||||
|
|
||||||
|
self.position_displacement = (
|
||||||
|
(0, 0, 0) if (displacement is None) else displacement
|
||||||
|
)
|
||||||
|
"""声像位移"""
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def set_info(self, sth: Any):
|
||||||
|
"""设置附加信息"""
|
||||||
|
self.extra_info = sth
|
||||||
|
|
||||||
|
def __str__(self, is_displacement: bool = False, is_track: bool = False):
|
||||||
|
return "{}Note(Instrument = {}, {}Velocity = {}, StartTick = {}, Duration = {}{}{})".format(
|
||||||
|
"Percussive" if self.percussive else "",
|
||||||
|
self.sound_name,
|
||||||
|
"" if self.percussive else "NotePitch = {}, ".format(self.note_pitch),
|
||||||
|
self.start_tick,
|
||||||
|
self.duration,
|
||||||
|
", Track = {}".format(self.track_no) if is_track else "",
|
||||||
|
(
|
||||||
|
", PositionDisplacement = {}".format(self.position_displacement)
|
||||||
|
if is_displacement
|
||||||
|
else ""
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def tuplize(self, is_displacement: bool = False, is_track: bool = False):
|
||||||
|
tuplized = self.__tuple__()
|
||||||
|
return (
|
||||||
|
tuplized[:-2]
|
||||||
|
+ ((tuplized[-2],) if is_track else ())
|
||||||
|
+ ((tuplized[-1],) if is_displacement else ())
|
||||||
|
)
|
||||||
|
|
||||||
|
def __list__(self) -> List:
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
self.percussive,
|
||||||
|
self.sound_name,
|
||||||
|
self.velocity,
|
||||||
|
self.start_tick,
|
||||||
|
self.duration,
|
||||||
|
self.track_no,
|
||||||
|
self.position_displacement,
|
||||||
|
]
|
||||||
|
if self.percussive
|
||||||
|
else [
|
||||||
|
self.percussive,
|
||||||
|
self.sound_name,
|
||||||
|
self.note_pitch,
|
||||||
|
self.velocity,
|
||||||
|
self.start_tick,
|
||||||
|
self.duration,
|
||||||
|
self.track_no,
|
||||||
|
self.position_displacement,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def __tuple__(
|
||||||
|
self,
|
||||||
|
) -> Union[
|
||||||
|
Tuple[bool, str, int, int, int, int, int, Tuple[float, float, float]],
|
||||||
|
Tuple[bool, str, int, int, int, int, Tuple[float, float, float]],
|
||||||
|
]:
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
self.percussive,
|
||||||
|
self.sound_name,
|
||||||
|
self.velocity,
|
||||||
|
self.start_tick,
|
||||||
|
self.duration,
|
||||||
|
self.track_no,
|
||||||
|
self.position_displacement,
|
||||||
|
)
|
||||||
|
if self.percussive
|
||||||
|
else (
|
||||||
|
self.percussive,
|
||||||
|
self.sound_name,
|
||||||
|
self.note_pitch,
|
||||||
|
self.velocity,
|
||||||
|
self.start_tick,
|
||||||
|
self.duration,
|
||||||
|
self.track_no,
|
||||||
|
self.position_displacement,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __dict__(self):
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
"Percussive": self.percussive,
|
||||||
|
"Instrument": self.sound_name,
|
||||||
|
"Velocity": self.velocity,
|
||||||
|
"StartTick": self.start_tick,
|
||||||
|
"Duration": self.duration,
|
||||||
|
"Track": self.track_no,
|
||||||
|
"PositionDisplacement": self.position_displacement,
|
||||||
|
}
|
||||||
|
if self.percussive
|
||||||
|
else {
|
||||||
|
"Percussive": self.percussive,
|
||||||
|
"Instrument": self.sound_name,
|
||||||
|
"Pitch": self.note_pitch,
|
||||||
|
"Velocity": self.velocity,
|
||||||
|
"StartTick": self.start_tick,
|
||||||
|
"Duration": self.duration,
|
||||||
|
"Track": self.track_no,
|
||||||
|
"PositionDisplacement": self.position_displacement,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other) -> bool:
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return False
|
||||||
|
return self.tuplize() == other.tuplize()
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class SingleNote:
|
class SingleNote:
|
||||||
"""存储单个音符的类"""
|
"""存储单个音符的类"""
|
||||||
@ -59,8 +254,8 @@ class SingleNote:
|
|||||||
velocity: int,
|
velocity: int,
|
||||||
startime: int,
|
startime: int,
|
||||||
lastime: int,
|
lastime: int,
|
||||||
|
is_percussion: bool,
|
||||||
track_number: int = 0,
|
track_number: int = 0,
|
||||||
is_percussion: Optional[bool] = None,
|
|
||||||
extra_information: Any = None,
|
extra_information: Any = None,
|
||||||
):
|
):
|
||||||
"""用于存储单个音符的类
|
"""用于存储单个音符的类
|
||||||
@ -82,12 +277,7 @@ class SingleNote:
|
|||||||
"""音符持续时间 ms"""
|
"""音符持续时间 ms"""
|
||||||
self.track_no: int = track_number
|
self.track_no: int = track_number
|
||||||
"""音符所处的音轨"""
|
"""音符所处的音轨"""
|
||||||
|
self.percussive: bool = is_percussion
|
||||||
self.percussive = (
|
|
||||||
(is_percussion in MC_PERCUSSION_INSTRUMENT_LIST)
|
|
||||||
if (is_percussion is None)
|
|
||||||
else is_percussion
|
|
||||||
)
|
|
||||||
"""是否为打击乐器"""
|
"""是否为打击乐器"""
|
||||||
|
|
||||||
self.extra_info = extra_information
|
self.extra_info = extra_information
|
||||||
@ -176,7 +366,7 @@ class SingleNote:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class SingleCommand:
|
class MineCommand:
|
||||||
"""存储单个指令的类"""
|
"""存储单个指令的类"""
|
||||||
|
|
||||||
command_text: str
|
command_text: str
|
||||||
@ -218,7 +408,7 @@ class SingleCommand:
|
|||||||
self.annotation_text = annotation
|
self.annotation_text = annotation
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return SingleCommand(
|
return MineCommand(
|
||||||
command=self.command_text,
|
command=self.command_text,
|
||||||
condition=self.conditional,
|
condition=self.conditional,
|
||||||
tick_delay=self.delay,
|
tick_delay=self.delay,
|
||||||
@ -399,3 +589,14 @@ NoteChannelType = Mapping[
|
|||||||
|
|
||||||
Dict[int,Dict[int,List[SingleNote,],],]
|
Dict[int,Dict[int,List[SingleNote,],],]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
MineNoteChannelType = Mapping[
|
||||||
|
int,
|
||||||
|
List[MineNote,],
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
我的世界频道信息类型
|
||||||
|
|
||||||
|
Dict[int,Dict[int,List[MineNote,],],]
|
||||||
|
"""
|
||||||
|
@ -18,8 +18,8 @@ Terms & Conditions: License.md in the root directory
|
|||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from .constants import MM_INSTRUMENT_RANGE_TABLE, MC_INSTRUMENT_BLOCKS_TABLE
|
from .constants import MC_INSTRUMENT_BLOCKS_TABLE, MM_INSTRUMENT_RANGE_TABLE
|
||||||
from .subclass import SingleNote
|
from .subclass import SingleNote, MineNote
|
||||||
|
|
||||||
from .types import (
|
from .types import (
|
||||||
Any,
|
Any,
|
||||||
@ -74,23 +74,25 @@ def inst_to_sould_with_deviation(
|
|||||||
-------
|
-------
|
||||||
tuple(str我的世界乐器名, int转换算法中的X)
|
tuple(str我的世界乐器名, int转换算法中的X)
|
||||||
"""
|
"""
|
||||||
return reference_table.get(
|
return (
|
||||||
instrumentID,
|
reference_table.get(
|
||||||
default=default_instrument,
|
instrumentID,
|
||||||
), 6
|
default_instrument,
|
||||||
|
),
|
||||||
|
6,
|
||||||
|
)
|
||||||
|
|
||||||
# 明明已经走了
|
# 明明已经走了
|
||||||
# 凭什么还要在我心里留下缠绵缱绻
|
# 凭什么还要在我心里留下缠绵缱绻
|
||||||
|
|
||||||
|
|
||||||
def midi_inst_to_mc_sould(
|
def midi_inst_to_mc_sound(
|
||||||
instrumentID: int,
|
instrumentID: int,
|
||||||
reference_table: MidiInstrumentTableType,
|
reference_table: MidiInstrumentTableType,
|
||||||
default_instrument: str = "note.flute",
|
default_instrument: str = "note.flute",
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
返回midi的乐器ID对应的我的世界乐器名,对于音域转换算法,如下:
|
返回midi的乐器ID对应的我的世界乐器名
|
||||||
2**( ( msg.note - 66 ) / 12 ) 即为MC的音高
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@ -101,17 +103,13 @@ def midi_inst_to_mc_sould(
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
tuple(str我的世界乐器名, int转换算法中的X)
|
str我的世界乐器名
|
||||||
"""
|
"""
|
||||||
return reference_table.get(
|
return reference_table.get(
|
||||||
instrumentID,
|
instrumentID,
|
||||||
default=default_instrument,
|
default_instrument,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 明明已经走了
|
|
||||||
# 凭什么还要在我心里留下缠绵缱绻
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def natural_curve(
|
def natural_curve(
|
||||||
vol: float,
|
vol: float,
|
||||||
@ -157,44 +155,167 @@ def straight_line(vol: float) -> float:
|
|||||||
return vol / -8 + 16
|
return vol / -8 + 16
|
||||||
|
|
||||||
|
|
||||||
def note_to_command_parameters(
|
def minenote_to_command_paramaters(
|
||||||
note_: SingleNote,
|
note_: MineNote,
|
||||||
reference_table: MidiInstrumentTableType,
|
pitch_deviation: float = 0,
|
||||||
deviation: int = 0,
|
|
||||||
volume_percentage: float = 1,
|
|
||||||
volume_processing_method: Callable[[float], float] = natural_curve,
|
|
||||||
) -> Tuple[
|
) -> Tuple[
|
||||||
str,
|
str,
|
||||||
float,
|
Tuple[float, float, float],
|
||||||
float,
|
float,
|
||||||
Union[float, Literal[None]],
|
Union[float, Literal[None]],
|
||||||
]:
|
]:
|
||||||
"""
|
"""
|
||||||
将音符转为播放的指令
|
将MineNote对象转为《我的世界》音符播放所需之参数
|
||||||
|
:param note_:MineNote 音符对象
|
||||||
|
:param deviation:float 音调偏移量
|
||||||
|
|
||||||
|
:return str[我的世界音符ID], Tuple[float,float,float]播放视角坐标, float[指令音量参数], float[指令音调参数]
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
note_.sound_name,
|
||||||
|
note_.position_displacement,
|
||||||
|
note_.velocity / 127,
|
||||||
|
(
|
||||||
|
None
|
||||||
|
if note_.percussive
|
||||||
|
else 2 ** ((note_.note_pitch - 66 + pitch_deviation) / 12)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def single_note_to_command_parameters(
|
||||||
|
note_: SingleNote,
|
||||||
|
reference_table: MidiInstrumentTableType,
|
||||||
|
deviation: float = 0,
|
||||||
|
volume_processing_method: Callable[[float], float] = natural_curve,
|
||||||
|
) -> Tuple[
|
||||||
|
str,
|
||||||
|
Tuple[float, float, float],
|
||||||
|
float,
|
||||||
|
Union[float, Literal[None]],
|
||||||
|
]:
|
||||||
|
"""
|
||||||
|
将音符转为播放的指令之参数
|
||||||
:param note_:int 音符对象
|
:param note_:int 音符对象
|
||||||
:param reference_table:Dict[int, str] 转换对照表
|
:param reference_table:Dict[int, str] 转换对照表
|
||||||
:param deviation:int 音调偏移量
|
:param deviation:float 音调偏移量
|
||||||
:param volume_percentage:int 音量占比(0,1]
|
|
||||||
:param volume_proccessing_method:Callable[[float], float] 音量处理函数
|
:param volume_proccessing_method:Callable[[float], float] 音量处理函数
|
||||||
|
|
||||||
:return str[我的世界音符ID], float[播放距离], float[指令音量参数], float[指令音调参数]
|
:return str[我的世界音符ID], Tuple[float,float,float]播放视角坐标, float[指令音量参数], float[指令音调参数]
|
||||||
"""
|
"""
|
||||||
mc_sound_ID = midi_inst_to_mc_sould(
|
|
||||||
|
mc_sound_ID = midi_inst_to_mc_sound(
|
||||||
note_.inst,
|
note_.inst,
|
||||||
reference_table,
|
reference_table,
|
||||||
"note.bd" if note_.percussive else "note.flute",
|
"note.bd" if note_.percussive else "note.flute",
|
||||||
)
|
)
|
||||||
|
|
||||||
# delaytime_now = round(self.start_time / float(speed) / 50)
|
mc_distance_volume = volume_processing_method(note_.velocity)
|
||||||
mc_pitch = None if note_.percussive else 2 ** ((note_.note - 66 + deviation) / 12)
|
|
||||||
|
|
||||||
mc_distance_volume = volume_processing_method(note_.velocity * volume_percentage)
|
return (
|
||||||
|
mc_sound_ID,
|
||||||
return mc_sound_ID, mc_distance_volume, volume_percentage, mc_pitch
|
(0, mc_distance_volume, 0),
|
||||||
|
note_.velocity / 127,
|
||||||
|
None if note_.percussive else 2 ** ((note_.pitch - 66 + deviation) / 12),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def from_single_note(
|
def midi_msgs_to_minenote(
|
||||||
note_: SingleNote, random_select: bool = False, default_block: str = "air"
|
inst_: int, # 乐器编号
|
||||||
|
note_: int,
|
||||||
|
percussive_: bool, # 是否作为打击乐器启用
|
||||||
|
velocity_: int,
|
||||||
|
start_time_: int,
|
||||||
|
duration_: int,
|
||||||
|
track_no_: int,
|
||||||
|
play_speed: float,
|
||||||
|
midi_reference_table: MidiInstrumentTableType,
|
||||||
|
volume_processing_method_: Callable[[float], float],
|
||||||
|
) -> MineNote:
|
||||||
|
"""
|
||||||
|
将Midi信息转为我的世界音符对象
|
||||||
|
:param inst_: int 乐器编号
|
||||||
|
:param note_: int 音高编号(音符编号)
|
||||||
|
:param percussive_: bool 是否作为打击乐器启用
|
||||||
|
:param velocity_: int 力度(响度)
|
||||||
|
:param start_time_: int 音符起始时间(毫秒数)
|
||||||
|
:param duration_: int 音符持续时间(毫秒数)
|
||||||
|
:param track_no_: int 音符所处音轨
|
||||||
|
:param play_speed: float 曲目播放速度
|
||||||
|
:param midi_reference_table: Dict[int, str] 转换对照表
|
||||||
|
:param volume_proccessing_method_: Callable[[float], float] 音量处理函数
|
||||||
|
|
||||||
|
:return MineNote我的世界音符对象
|
||||||
|
"""
|
||||||
|
mc_sound_ID = midi_inst_to_mc_sound(
|
||||||
|
inst_,
|
||||||
|
midi_reference_table,
|
||||||
|
"note.bd" if percussive_ else "note.flute",
|
||||||
|
)
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def single_note_to_minenote(
|
||||||
|
note_: SingleNote,
|
||||||
|
reference_table: MidiInstrumentTableType,
|
||||||
|
play_speed: float = 0,
|
||||||
|
volume_processing_method: Callable[[float], float] = natural_curve,
|
||||||
|
) -> MineNote:
|
||||||
|
"""
|
||||||
|
将音符转为我的世界音符对象
|
||||||
|
:param note_:SingleNote 音符对象
|
||||||
|
:param reference_table:Dict[int, str] 转换对照表
|
||||||
|
:param play_speed:float 播放速度
|
||||||
|
:param volume_proccessing_method:Callable[[float], float] 音量处理函数
|
||||||
|
|
||||||
|
:return MineNote我的世界音符对象
|
||||||
|
"""
|
||||||
|
mc_sound_ID = midi_inst_to_mc_sound(
|
||||||
|
note_.inst,
|
||||||
|
reference_table,
|
||||||
|
"note.bd" if note_.percussive else "note.flute",
|
||||||
|
)
|
||||||
|
|
||||||
|
mc_distance_volume = volume_processing_method(note_.velocity)
|
||||||
|
|
||||||
|
return MineNote(
|
||||||
|
mc_sound_ID,
|
||||||
|
note_.pitch,
|
||||||
|
note_.velocity,
|
||||||
|
round(note_.start_time / float(play_speed) / 50),
|
||||||
|
round(note_.duration / float(play_speed) / 50),
|
||||||
|
note_.track_no,
|
||||||
|
note_.percussive,
|
||||||
|
(0, mc_distance_volume, 0),
|
||||||
|
note_.extra_info,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_in_diapason(note_pitch: int, instrument: str) -> bool:
|
||||||
|
note_range = MM_INSTRUMENT_RANGE_TABLE.get(instrument, ((-1, 128), 0))[0]
|
||||||
|
return note_pitch >= note_range[0] and note_pitch <= note_range[1]
|
||||||
|
|
||||||
|
|
||||||
|
def is_note_in_diapason(note_: MineNote) -> bool:
|
||||||
|
note_range = MM_INSTRUMENT_RANGE_TABLE.get(note_.sound_name, ((-1, 128), 0))[0]
|
||||||
|
return note_.note_pitch >= note_range[0] and note_.note_pitch <= note_range[1]
|
||||||
|
|
||||||
|
|
||||||
|
def note_to_redstone_block(
|
||||||
|
note_: MineNote, random_select: bool = False, default_block: str = "air"
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
将我的世界乐器名改作音符盒所需的对应方块名称
|
将我的世界乐器名改作音符盒所需的对应方块名称
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</img>
|
</img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 align="center">一款免费开源的《我的世界》数字音频库。</h3>
|
<h3 align="center">一款免费开源的《我的世界》数字音频支持库。</h3>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
||||||
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
## 介绍 🚀
|
## 介绍 🚀
|
||||||
|
|
||||||
音·创 是一个免费开源的针对 **《我的世界》** 的 MIDI 音乐转换库
|
音·创 是一款免费开源的针对 **《我的世界》** 音乐的支持库
|
||||||
|
|
||||||
欢迎加群:[861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
欢迎加群:[861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
|
|||||||
|
|
||||||
NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
|
NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
|
||||||
|
|
||||||
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-00A1E7?style=for-the-badge
|
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
|
||||||
[Bilibili: 诸葛亮与八卦阵]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
|
[Bilibili: 诸葛亮与八卦阵]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
|
||||||
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
|
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
|
||||||
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
|
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
## Introduction🚀
|
## Introduction🚀
|
||||||
|
|
||||||
Musicreater is a free open-source library used for converting digital music files into formats that could be read in _Minecraft_.
|
Musicreater is a free open-source library used for digital music that being played in _Minecraft_.
|
||||||
|
|
||||||
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
|
|||||||
|
|
||||||
- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易网络科技发展有限公司
|
- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易网络科技发展有限公司
|
||||||
|
|
||||||
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-00A1E7?style=for-the-badge
|
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
|
||||||
[Bilibili: bgArray]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
|
[Bilibili: bgArray]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
|
||||||
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
|
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
|
||||||
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
|
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import Musicreater.experiment
|
|
||||||
|
|
||||||
|
|
||||||
print(
|
|
||||||
Musicreater.experiment.FutureMidiConvertRSNB.from_midi_file(
|
|
||||||
input("midi路径:"), old_exe_format=False
|
|
||||||
).to_note_list_in_delay()
|
|
||||||
)
|
|
43
example.py
43
example.py
@ -80,11 +80,11 @@ else:
|
|||||||
# 提示语 检测函数 错误提示语
|
# 提示语 检测函数 错误提示语
|
||||||
for args in [
|
for args in [
|
||||||
(
|
(
|
||||||
f"输入音量:",
|
f"最小播放音量:",
|
||||||
float,
|
float,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
f"输入播放速度:",
|
f"播放速度:",
|
||||||
float,
|
float,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -118,21 +118,19 @@ else:
|
|||||||
if fileFormat == 1
|
if fileFormat == 1
|
||||||
else (
|
else (
|
||||||
(
|
(
|
||||||
(
|
"结构延展方向:",
|
||||||
"结构延展方向:",
|
lambda a: isin(
|
||||||
lambda a: isin(
|
a,
|
||||||
a,
|
{
|
||||||
{
|
"z+": ["z+", "Z+"],
|
||||||
"z+": ["z+", "Z+"],
|
"x+": ["X+", "x+"],
|
||||||
"x+": ["X+", "x+"],
|
"z-": ["Z-", "z-"],
|
||||||
"z-": ["Z-", "z-"],
|
"x-": ["x-", "X-"],
|
||||||
"x-": ["x-", "X-"],
|
},
|
||||||
},
|
),
|
||||||
),
|
)
|
||||||
)
|
if (playerFormat == 2 and fileFormat == 2)
|
||||||
if (playerFormat == 2 and fileFormat == 2)
|
else ()
|
||||||
else ()
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -152,12 +150,17 @@ else:
|
|||||||
),
|
),
|
||||||
]:
|
]:
|
||||||
if args:
|
if args:
|
||||||
prompts.append(args[1](input(args[0])))
|
try:
|
||||||
|
prompts.append(args[1](input(args[0])))
|
||||||
|
except Exception:
|
||||||
|
print(args)
|
||||||
|
|
||||||
|
|
||||||
print(f"正在处理 {midi_path} :")
|
print(f"正在处理 {midi_path} :")
|
||||||
cvt_mid = Musicreater.MidiConvert.from_midi_file(midi_path, old_exe_format=False)
|
cvt_mid = Musicreater.MidiConvert.from_midi_file(
|
||||||
cvt_cfg = ConvertConfig(out_path, *prompts[:3])
|
midi_path, old_exe_format=False, min_volume=prompts[0], play_speed=prompts[1]
|
||||||
|
)
|
||||||
|
cvt_cfg = ConvertConfig(out_path, prompts[2])
|
||||||
|
|
||||||
if fileFormat == 0:
|
if fileFormat == 0:
|
||||||
if playerFormat == 1:
|
if playerFormat == 1:
|
||||||
|
@ -7,7 +7,7 @@ print(
|
|||||||
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
|
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
|
||||||
input("midi路径:"), old_exe_format=False
|
input("midi路径:"), old_exe_format=False
|
||||||
),
|
),
|
||||||
Musicreater.plugin.ConvertConfig(input("输出路径:"), volume=1),
|
Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||||
max_height=32,
|
max_height=32,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
2
setup.py
2
setup.py
@ -15,7 +15,7 @@ setuptools.setup(
|
|||||||
version=Musicreater.__version__,
|
version=Musicreater.__version__,
|
||||||
author="Eilles Wan, bgArray",
|
author="Eilles Wan, bgArray",
|
||||||
author_email="TriM-Organization@hotmail.com",
|
author_email="TriM-Organization@hotmail.com",
|
||||||
description="一款免费开源的 《我的世界》 mid音乐转换库。\n"
|
description="一款免费开源的针对《我的世界》音乐的支持库\n"
|
||||||
"A free open-source python library used to convert midi into Minecraft.",
|
"A free open-source python library used to convert midi into Minecraft.",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user