2.0.0全新版本,更好的代码组织形式

This commit is contained in:
EillesWan 2024-03-04 00:04:05 +08:00
parent 3831c41b9a
commit 95c0ff1b47
23 changed files with 815 additions and 414 deletions

View File

@ -17,8 +17,8 @@ Terms & Conditions: License.md in the root directory
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__version__ = "1.8"
__vername__ = "音调总体偏移的处理支持"
__version__ = "2.0.0-alpha"
__vername__ = "全新组织架构"
__author__ = (
("金羿", "Eilles Wan"),
("诸葛亮与八卦阵", "bgArray"),
@ -31,7 +31,8 @@ __all__ = [
"MidiConvert",
# 附加类
"SingleNote",
"SingleCommand",
"MineNote",
"MineCommand",
"SingleNoteBox",
# "TimeStamp", 未来功能
# 默认值

View File

@ -414,28 +414,28 @@ MC_INSTRUMENT_BLOCKS_TABLE: Dict[str, Tuple[str, ...]] = {
# Midi对MC通用对照表
MM_INSTRUMENT_RANGE_TABLE: Dict[str, Tuple[int, int]] = {
"note.harp": (42, 66),
"note.pling": (42, 66),
"note.guitar": (30, 54),
"note.iron_xylophone": (42, 66),
"note.bell": (66, 90),
"note.xylophone": (66, 90),
"note.chime": (66, 90),
"note.banjo": (42, 66),
"note.flute": (54, 78),
"note.bass": (18, 42),
"note.snare": (-1, 128), # 实际上是 0~127
"note.didgeridoo": (18, 42),
"mob.zombie.wood": (-1, 128),
"note.bit": (42, 66),
"note.hat": (-1, 128),
"note.bd": (-1, 128),
"note.basedrum": (-1, 128),
"firework.blast": (-1, 128),
"firework.twinkle": (-1, 128),
"fire.ignite": (-1, 128),
"note.cow_bell": (54, 78),
MM_INSTRUMENT_RANGE_TABLE: Dict[str, Tuple[Tuple[int, int], int]] = {
"note.harp": ((42, 66), 54),
"note.pling": ((42, 66), 54),
"note.guitar": ((30, 54), 42),
"note.iron_xylophone": ((42, 66), 54),
"note.bell": ((66, 90), 78),
"note.xylophone": ((66, 90), 78),
"note.chime": ((66, 90), 78),
"note.banjo": ((42, 66), 54),
"note.flute": ((54, 78), 66),
"note.bass": ((18, 42), 30),
"note.snare": ((-1, 128), 0), # 实际上是 0~127
"note.didgeridoo": ((18, 42), 30),
"mob.zombie.wood": ((-1, 128), 0),
"note.bit": ((42, 66), 54),
"note.hat": ((-1, 128), 0),
"note.bd": ((-1, 128), 0),
"note.basedrum": ((-1, 128), 0),
"firework.blast": ((-1, 128), 0),
"firework.twinkle": ((-1, 128), 0),
"fire.ignite": ((-1, 128), 0),
"note.cow_bell": ((54, 78), 66),
}
"""不同乐器的音域偏离对照表"""

View File

@ -115,3 +115,11 @@ class ZeroSpeedError(ZeroDivisionError):
def __init__(self, *args):
"""以0作为播放速度的错误"""
super().__init__("播放速度为0", *args)
class IllegalMinimumVolumeError(ValueError):
"""最小播放音量有误的错误"""
def __init__(self, *args):
"""最小播放音量错误"""
super().__init__("最小播放音量超出范围", *args)

View File

@ -95,19 +95,13 @@ class FutureMidiConvertM4(MidiConvert):
def to_command_list_in_delay(
self,
max_volume: float = 1.0,
speed: float = 1.0,
player_selector: str = "@a",
) -> Tuple[List[SingleCommand], int, int]:
) -> Tuple[List[MineCommand], int, int]:
"""
将midi转换为我的世界命令列表并输出每个音符之后的延迟
Parameters
----------
max_volume: float
最大播放音量注意这里的音量范围为(0,1]如果超出将被处理为正确值其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed
player_selector: str
玩家选择器默认为`@a`
@ -116,17 +110,13 @@ class FutureMidiConvertM4(MidiConvert):
tuple( 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)
notes_list: List[SingleNote] = []
notes_list: List[MineNote] = []
# 此处 我们把通道视为音轨
for channel in self.channels.values():
for note in channel:
note.set_info(
note_to_command_parameters(
single_note_to_note_parameters(
note,
(
self.percussion_note_referrence_table
@ -160,7 +150,7 @@ class FutureMidiConvertM4(MidiConvert):
max_multi = max(max_multi, multi)
multi = 0
self.music_command_list.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(player_selector)
+ r"playsound {} @s ^ ^ ^{} {} {}".format(*note.extra_info),
tick_delay=tickdelay,
@ -173,8 +163,7 @@ class FutureMidiConvertM4(MidiConvert):
)
delaytime_previous = delaytime_now
self.music_tick_num = round(notes_list[-1].start_time / speed / 50)
return self.music_command_list, self.music_tick_num, max_multi + 1
return self.music_command_list, round(notes_list[-1].start_time / speed / 50), max_multi + 1
class FutureMidiConvertM5(MidiConvert):
@ -267,7 +256,7 @@ class FutureMidiConvertM5(MidiConvert):
max_volume: float = 1.0,
speed: float = 1.0,
player_selector: str = "@a",
) -> Tuple[List[SingleCommand], int]:
) -> Tuple[List[MineCommand], int]:
"""
使用金羿的转换思路使用同刻偏移算法优化音感后将midi转换为我的世界命令列表并输出每个音符之后的延迟
@ -314,11 +303,11 @@ class FutureMidiConvertM5(MidiConvert):
elif msg[0] == "NoteS":
soundID = (
midi_inst_to_mc_sould(
midi_inst_to_mc_sound(
msg[1], MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
)
if SpecialBits
else midi_inst_to_mc_sould(
else midi_inst_to_mc_sound(
InstID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
)
)
@ -354,7 +343,7 @@ class FutureMidiConvertM5(MidiConvert):
for i in range(len(all_ticks)):
for j in range(len(tracks[all_ticks[i]])):
results.append(
SingleCommand(
MineCommand(
tracks[all_ticks[i]][j],
tick_delay=(
(
@ -402,8 +391,7 @@ class FutureMidiConvertM5(MidiConvert):
)
self.music_command_list = results
self.music_tick_num = max(all_ticks)
return results, self.music_tick_num
return results, max(all_ticks)
class FutureMidiConvertM6(MidiConvert):

View File

@ -3,9 +3,9 @@
"""
· (Musicreater)
一款免费开源的针对我的世界的midi音乐转换
一款免费开源的针对我的世界音乐的支持
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 · 开发者
Copyright © 2024 all the developers of Musicreater
@ -19,10 +19,10 @@ Terms & Conditions: License.md in the root directory
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
# BUG退散BUG退散 BUG退散BUG退散
# 异常、错误作乱之时 異常、誤りが、困った時は
# 二六字组!万国码合!二六字组!万国码合! グループ!コード#!グループ!コード#!
# 赶快呼叫 程序员Let's Go 直ぐに呼びましょプログラマ レッツゴー!
# BUG退散BUG退散 BUG退散BUG退散 BUG retreat! BUG retreat!
# 异常与错误作乱之时 異常、誤りが、困った時は Abnormalities and errors are causing chaos
# 二六字组!万国码合!二六字组!万国码合! グループ!コード#!グループ!コード#! Words combine! Unicode unite!
# 赶快呼叫 程序员Let's Go 直ぐに呼びましょプログラマ レッツゴー! Hurry to call the programmer! Let's Go!
import math
@ -84,49 +84,192 @@ class MusicSequence:
"""
music_name: str
"""Midi乐曲名"""
"""乐曲名"""
channels: NoteChannelType
channels: MineNoteChannelType
"""频道信息字典"""
total_note_count: int
"""音符总数"""
used_instrument: List[str]
"""所使用的乐器"""
minium_volume: float
"""乐曲最小音量"""
music_deviation: float
"""乐曲音调偏移"""
def __init__(
self,
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:
"""
我的世界音符序列类
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.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
def from_mido(
cls,
mido_file: mido.MidiFile,
midi_music_name,
default_tempo: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
midi_music_name: str,
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,
):
return cls(
midi_music_name,
note_channels, note_count_total, inst_note_count, qualified_inst_note_count = (
cls.to_music_note_channels(
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,
vol_processing_function=volume_processing_function,
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):
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 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
def to_music_note_channels(
midi: mido.MidiFile,
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
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解析并转换为频道音符字典
@ -136,14 +279,20 @@ class MusicSequence:
Dict[int,List[SingleNote,]]
"""
if speed == 0:
raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。")
# if midi is None:
# raise MidiUnboundError(
# "Midi参量为空。你是否正在使用的是一个由 copy_important 生成的MidiConvert对象这是不可复用的。"
# )
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
midi_channels: NoteChannelType = empty_midi_channels(staff=[])
midi_channels: MineNoteChannelType = empty_midi_channels(staff=[])
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])
)
note_queue_B[msg.channel].remove((_velocity, _ms))
midi_channels[msg.channel].append(
SingleNote(
instrument=msg.note,
pitch=channel_program[msg.channel],
velocity=_velocity,
startime=_ms,
lastime=microseconds - _ms,
track_number=track_no,
is_percussion=True,
)
if msg.channel == 9
else SingleNote(
instrument=channel_program[msg.channel],
pitch=msg.note,
velocity=_velocity,
startime=_ms,
lastime=microseconds - _ms,
track_number=track_no,
is_percussion=False,
that_note := midi_msgs_to_minenote(
inst_=(
msg.note
if msg.channel == 9
else channel_program[msg.channel]
),
note_=(
channel_program[msg.channel]
if msg.channel == 9
else msg.note
),
velocity_=_velocity,
start_time_=_ms,
duration_=microseconds - _ms,
track_no_=track_no,
percussive_=(msg.channel == 9),
play_speed=speed,
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:
if ignore_mismatch_error:
print(
@ -254,12 +423,17 @@ class MusicSequence:
del tempo
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()
]
)
return channels
return (
channels,
note_count,
note_count_per_instruments,
qualified_note_count_per_instruments,
)
class MidiConvert(MusicSequence):
@ -267,42 +441,31 @@ class MidiConvert(MusicSequence):
将Midi文件转换为我的世界内容
"""
pitched_note_reference_table: MidiInstrumentTableType
"""乐音乐器Midi-MC对照表"""
percussion_note_referrence_table: MidiInstrumentTableType
"""打击乐器Midi-MC对照表"""
volume_processing_function: FittingFunctionType
"""音量处理函数"""
enable_old_exe_format: bool
"""是否启用旧版execute指令格式"""
execute_cmd_head: str
"""execute指令头部"""
music_command_list: List[SingleCommand]
music_command_list: List[MineCommand]
"""音乐指令列表"""
music_tick_num: int
"""音乐总延迟"""
progress_bar_command: List[SingleCommand]
progress_bar_command: List[MineCommand]
"""进度条指令列表"""
music_deviation: int
"""音乐音调总偏移"""
def __init__(
self,
@classmethod
def from_mido_obj(
cls,
midi_obj: mido.MidiFile,
midi_name: str,
deviation: Union[int, Literal[None]] = 0,
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,
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,
):
"""
@ -322,40 +485,42 @@ class MidiConvert(MusicSequence):
打击乐器Midi-MC对照表
"""
super(MidiConvert, self).from_mido(
midi_obj,
midi_name,
mismatch_error_ignorance=ignore_mismatch_error,
)
cls.enable_old_exe_format: bool = enable_old_exe_format
self.music_deviation = (
self.guess_deviation() if deviation is None else deviation
)
self.enable_old_exe_format: bool = enable_old_exe_format
self.execute_cmd_head = (
cls.execute_cmd_head = (
"execute {} ~ ~ ~ "
if enable_old_exe_format
else "execute as {} at @s positioned ~ ~ ~ run "
)
self.pitched_note_reference_table = pitched_note_rtable
self.percussion_note_referrence_table = percussion_note_rtable
self.volume_processing_function = vol_processing_function
cls.progress_bar_command = cls.music_command_list = []
cls.channels = {}
self.progress_bar_command = self.music_command_list = []
self.channels = {}
self.music_tick_num = 0
return cls.from_mido(
mido_file=midi_obj,
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
def from_midi_file(
cls,
midi_file_path: str,
ignore_mismatch_error: bool = True,
old_exe_format: bool = False,
mismatch_error_ignorance: bool = True,
play_speed: float = 1,
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,
):
"""
@ -363,14 +528,19 @@ class MidiConvert(MusicSequence):
Parameters
----------
midi_file: str
midi_file_path: str
midi文件地址
enable_old_exe_format: bool
是否启用旧版(1.19)指令格式默认为否
speed: float
速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed
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
是否启用旧版(1.19)指令格式默认为否
min_volume: float
最小播放音量注意这里的音量范围为(0,1]如果超出将被处理为正确值
"""
midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0].replace(
@ -379,18 +549,21 @@ class MidiConvert(MusicSequence):
"""文件名,不含路径且不含后缀"""
try:
return cls(
mido.MidiFile(
return cls.from_mido_obj(
midi_obj=mido.MidiFile(
midi_file_path,
clip=True,
),
midi_music_name,
None,
old_exe_format,
ignore_mismatch_error,
pitched_note_table,
percussion_note_table,
vol_processing_func,
midi_name=midi_music_name,
ignore_mismatch_error=mismatch_error_ignorance,
playment_speed=play_speed,
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,
vol_processing_function=vol_processing_func,
)
except (ValueError, TypeError) as 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(
self,
max_score: int,
scoreboard_name: str,
progressbar_style: ProgressBarStyle = DEFAULT_PROGRESSBAR_STYLE,
) -> List[SingleCommand]:
) -> List[MineCommand]:
"""
生成进度条
@ -446,7 +614,7 @@ class MidiConvert(MusicSequence):
perEach = max_score / pgs_style.count("_")
"""每个进度条代表的分值"""
result: List[SingleCommand] = []
result: List[MineCommand] = []
if r"%^s" in pgs_style:
pgs_style = pgs_style.replace(r"%^s", str(max_score))
@ -457,7 +625,7 @@ class MidiConvert(MusicSequence):
sbn_pc = scoreboard_name[:2]
if r"%%%" in pgs_style:
result.append(
SingleCommand(
MineCommand(
'scoreboard objectives add {}PercT dummy "百分比计算"'.format(
sbn_pc
),
@ -465,7 +633,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -476,7 +644,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -485,7 +653,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -496,7 +664,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -507,7 +675,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -524,7 +692,7 @@ class MidiConvert(MusicSequence):
if r"%%t" in pgs_style:
result.append(
SingleCommand(
MineCommand(
'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format(
sbn_pc
),
@ -532,7 +700,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(
sbn_pc
),
@ -540,7 +708,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -549,7 +717,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -559,7 +727,7 @@ class MidiConvert(MusicSequence):
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -570,7 +738,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -581,7 +749,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -593,7 +761,7 @@ class MidiConvert(MusicSequence):
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -605,7 +773,7 @@ class MidiConvert(MusicSequence):
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
@ -642,7 +810,7 @@ class MidiConvert(MusicSequence):
)
)
result.append(
SingleCommand(
MineCommand(
self.execute_cmd_head.format(
r"@a[scores={"
+ scoreboard_name
@ -658,20 +826,20 @@ class MidiConvert(MusicSequence):
if r"%%%" in pgs_style:
result.append(
SingleCommand(
MineCommand(
"scoreboard objectives remove {}PercT".format(sbn_pc),
annotation="移除临时百分比变量",
)
)
if r"%%t" in pgs_style:
result.append(
SingleCommand(
MineCommand(
"scoreboard objectives remove {}TMinT".format(sbn_pc),
annotation="移除临时分变量",
)
)
result.append(
SingleCommand(
MineCommand(
"scoreboard objectives remove {}TSecT".format(sbn_pc),
annotation="移除临时秒变量",
)
@ -683,9 +851,7 @@ class MidiConvert(MusicSequence):
def to_command_list_in_score(
self,
scoreboard_name: str = "mscplay",
max_volume: float = 1.0,
speed: float = 1.0,
) -> Tuple[List[List[SingleCommand]], int, int]:
) -> Tuple[List[List[MineCommand]], int, int]:
"""
将midi转换为我的世界命令列表
@ -693,20 +859,12 @@ class MidiConvert(MusicSequence):
----------
scoreboard_name: str
我的世界的计分板名称
max_volume: float
最大播放音量注意这里的音量范围为(0,1]如果超出将被处理为正确值其原理为在距离玩家 (1 / volume -1) 的地方播放
speed: float
速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed
Returns
-------
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_amount = 0
max_score = 0
@ -720,59 +878,44 @@ class MidiConvert(MusicSequence):
this_channel = []
for note in channel:
score_now = round(note.start_time / float(speed) / 50)
max_score = max(max_score, score_now)
max_score = max(max_score, note.start_tick)
(
mc_sound_ID,
mc_distance_volume,
relative_coordinates,
volume_percentage,
mc_pitch,
) = note_to_command_parameters(
) = minenote_to_command_paramaters(
note,
(
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,
pitch_deviation=self.music_deviation,
)
this_channel.append(
SingleCommand(
MineCommand(
(
self.execute_cmd_head.format(
"@a[scores=({}={})]".format(scoreboard_name, score_now)
"@a[scores=({}={})]".format(
scoreboard_name, note.start_tick
)
.replace("(", r"{")
.replace(")", r"}")
)
+ (
r"playsound {} @s ^ ^ ^{} {}".format(
mc_sound_ID, mc_distance_volume, volume_percentage
)
if note.percussive
else r"playsound {} @s ^ ^ ^{} {} {}".format(
mc_sound_ID,
mc_distance_volume,
volume_percentage,
mc_pitch,
)
+ r"playsound {} @s ^{} ^{} ^{} {} {} {}".format(
mc_sound_ID,
*relative_coordinates,
volume_percentage,
1.0 if note.percussive else mc_pitch,
self.minium_volume,
)
),
annotation=(
"{}播放{}%{}噪音".format(
mctick2timestr(score_now),
max_volume * 100,
"{}播放噪音{}".format(
mctick2timestr(note.start_tick),
mc_sound_ID,
)
if note.percussive
else "{}播放{}%{}乐音".format(
mctick2timestr(score_now),
max_volume * 100,
else "{}播放乐音{}".format(
mctick2timestr(note.start_tick),
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
)
),
@ -785,24 +928,17 @@ class MidiConvert(MusicSequence):
self.music_command_list.extend(this_channel)
command_channels.append(this_channel)
self.music_tick_num = max_score
return command_channels, command_amount, max_score
def to_command_list_in_delay(
self,
max_volume: float = 1.0,
speed: float = 1.0,
player_selector: str = "@a",
) -> Tuple[List[SingleCommand], int, int]:
) -> Tuple[List[MineCommand], int, int]:
"""
将midi转换为我的世界命令列表并输出每个音符之后的延迟
Parameters
----------
max_volume: float
最大播放音量注意这里的音量范围为(0,1]如果超出将被处理为正确值其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed
player_selector: str
玩家选择器默认为`@a`
@ -811,25 +947,18 @@ class MidiConvert(MusicSequence):
tuple( 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)
notes_list: List[SingleNote] = []
notes_list: List[MineNote] = sorted(
[i for j in self.channels.values() for i in j],
key=lambda note: note.start_tick,
)
# 此处 我们把通道视为音轨
for channel in self.channels.values():
notes_list.extend(channel)
notes_list.sort(key=lambda a: a.start_time)
self.music_command_list = []
multi = max_multi = 0
delaytime_previous = 0
for note in notes_list:
delaytime_now = round(note.start_time / speed / 50)
if (tickdelay := (delaytime_now - delaytime_previous)) == 0:
if (tickdelay := (note.start_tick - delaytime_previous)) == 0:
multi += 1
else:
max_multi = max(max_multi, multi)
@ -837,68 +966,53 @@ class MidiConvert(MusicSequence):
(
mc_sound_ID,
mc_distance_volume,
relative_coordinates,
volume_percentage,
mc_pitch,
) = note_to_command_parameters(
) = minenote_to_command_paramaters(
note,
(
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,
pitch_deviation=self.music_deviation,
)
self.music_command_list.append(
SingleCommand(
MineCommand(
command=(
self.execute_cmd_head.format(player_selector)
+ (
r"playsound {} @s ^ ^ ^{} {}".format(
mc_sound_ID, mc_distance_volume, volume_percentage
)
if note.percussive
else r"playsound {} @s ^ ^ ^{} {} {}".format(
mc_sound_ID,
mc_distance_volume,
volume_percentage,
mc_pitch,
)
+ r"playsound {} @s ^{} ^{} ^{} {} {} {}".format(
mc_sound_ID,
*relative_coordinates,
volume_percentage,
1.0 if note.percussive else mc_pitch,
self.minium_volume,
)
),
annotation=(
"{}播放{}%{}噪音".format(
mctick2timestr(delaytime_now),
max_volume * 100,
"{}播放噪音{}".format(
mctick2timestr(note.start_tick),
mc_sound_ID,
)
if note.percussive
else "{}播放{}%{}乐音".format(
mctick2timestr(delaytime_now),
max_volume * 100,
else "{}播放乐音{}".format(
mctick2timestr(note.start_tick),
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
)
),
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, self.music_tick_num, max_multi + 1
return self.music_command_list, notes_list[-1].start_tick, max_multi + 1
def copy_important(self):
dst = MidiConvert(
dst = MidiConvert.from_mido_obj(
midi_obj=mido.MidiFile(),
midi_name=self.music_name,
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.progress_bar_command = [i.copy() for i in self.progress_bar_command]
dst.music_tick_num = self.music_tick_num
return dst

View File

@ -56,7 +56,7 @@ def to_addon_pack_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存在时清空其下所有项目然后创建
@ -180,9 +180,7 @@ def to_addon_pack_in_delay(
)
command_list, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)[:2]
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(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)
if not os.path.exists(data_cfg.dist_path):

View File

@ -18,7 +18,7 @@ Terms & Conditions: License.md in the root directory
from typing import List
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
BDX_MOVE_KEY = {
@ -123,7 +123,7 @@ def form_command_block_in_BDX_bytes(
def commands_to_BDX_bytes(
commands_list: List[SingleCommand],
commands_list: List[MineCommand],
max_height: int = 64,
):
"""

View File

@ -17,7 +17,7 @@ import os
import brotli
from ...main import MidiConvert
from ...subclass import SingleCommand
from ...subclass import MineCommand
from ..bdx import (
bdx_move,
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(
scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier
scoreboard_name=scoreboard_name,
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")
),
os.path.abspath(os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")),
"w+",
) as f:
f.write("BD@")
@ -83,7 +81,7 @@ def to_BDX_file_in_score(
midi_cvt.music_command_list
+ (
[
SingleCommand(
MineCommand(
command="scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
@ -118,9 +116,7 @@ def to_BDX_file_in_score(
_bytes += cmdBytes
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")
),
os.path.abspath(os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")),
"ab+",
) as f:
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(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")
),
os.path.abspath(os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")),
"w+",
) as f:
f.write("BD@")
@ -216,9 +208,7 @@ def to_BDX_file_in_delay(
_bytes += cmdBytes
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")
),
os.path.abspath(os.path.join(data_cfg.dist_path, f"{midi_cvt.music_name}.bdx")),
"ab+",
) as f:
f.write(brotli.compress(_bytes + b"XE"))

View File

@ -23,17 +23,11 @@ from ..subclass import DEFAULT_PROGRESSBAR_STYLE, ProgressBarStyle
@dataclass(init=False)
class ConvertConfig:
class ConvertConfig: # 必定要改
"""
转换通用设置存储类
"""
volume_ratio: float
"""音量比例"""
speed_multiplier: float
"""速度倍率"""
progressbar_style: Union[ProgressBarStyle, None]
"""进度条样式"""
@ -43,8 +37,6 @@ class ConvertConfig:
def __init__(
self,
output_path: str,
volume: float = 1.0,
speed: float = 1.0,
progressbar: Union[bool, Tuple[str, Tuple[str, str]], ProgressBarStyle] = True,
ignore_progressbar_param_error: bool = False,
):
@ -67,12 +59,6 @@ class ConvertConfig:
self.dist_path = output_path
"""输出目录"""
self.volume_ratio = volume
"""音量比例"""
self.speed_multiplier = speed
"""速度倍率"""
if progressbar:
# 此处是对于仅有 True 的参数和自定义参数的判断
# 改这一段没🐎

View File

@ -21,4 +21,8 @@ __all__ = [
]
__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,
)

View File

@ -17,7 +17,7 @@ from typing import Literal
# from ...exceptions import CommandFormatError
from ...main import MidiConvert
from ..main import ConvertConfig
from ...subclass import SingleCommand
from ...subclass import MineCommand
from ..mcstructure import (
COMPABILITY_VERSION_117,
COMPABILITY_VERSION_119,
@ -58,9 +58,7 @@ def to_mcstructure_file_in_delay(
)
cmd_list, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)[:2]
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(
scoreboard_name,
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
scoreboard_name=scoreboard_name,
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
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={"
+ scoreboard_name
+ "="
@ -139,7 +136,9 @@ def to_mcstructure_file_in_score(
]
if auto_reset
else []
), max_height - 1, compability_version_=compability_ver
),
max_height - 1,
compability_version_=compability_ver,
)
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(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)
if not os.path.exists(data_cfg.dist_path):

View File

@ -21,7 +21,7 @@ from typing import List, Literal, Tuple
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
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
@ -226,7 +226,7 @@ def form_command_block_in_NBT_struct(
def commands_to_structure(
commands: List[SingleCommand],
commands: List[MineCommand],
max_height: int = 64,
compability_version_: int = COMPABILITY_VERSION_119,
):
@ -313,7 +313,7 @@ def commands_to_structure(
def commands_to_redstone_delay_structure(
commands: List[SingleCommand],
commands: List[MineCommand],
delay_length: int,
max_multicmd_length: int,
base_block: str = "concrete",

View File

@ -18,7 +18,7 @@ Terms & Conditions: License.md in the root directory
from ..exceptions import NotDefineProgramError, ZeroSpeedError
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
# 你以为写完了吗?其实并没有

View File

@ -15,4 +15,4 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import nbtschematic
# import nbtschematic

View File

@ -13,17 +13,17 @@ Terms & Conditions: License.md in the root directory
import fcwslib
from ...main import MidiConvert
# from ...main import MidiConvert
from ..main import ConvertConfig
from ...subclass import SingleCommand
# from ..main import ConvertConfig
# from ...subclass import MineCommand
def open_websocket_server(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
server_dist: str = "localhost",
server_port: int = 8000,
):
wssever = fcwslib.Server(server=server_dist,port=server_port,debug_mode=False)
# def open_websocket_server(
# midi_cvt: MidiConvert,
# data_cfg: ConvertConfig,
# player: str = "@a",
# server_dist: str = "localhost",
# server_port: int = 8000,
# ):
# wssever = fcwslib.Server(server=server_dist,port=server_port,debug_mode=False)

View File

@ -18,12 +18,207 @@ Terms & Conditions: License.md in the root directory
from dataclasses import dataclass
from typing import Any
from .types import Optional, Any, List, Mapping
from .types import Optional, Any, List, Mapping, Tuple, Union
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)
class SingleNote:
"""存储单个音符的类"""
@ -59,8 +254,8 @@ class SingleNote:
velocity: int,
startime: int,
lastime: int,
is_percussion: bool,
track_number: int = 0,
is_percussion: Optional[bool] = None,
extra_information: Any = None,
):
"""用于存储单个音符的类
@ -82,12 +277,7 @@ class SingleNote:
"""音符持续时间 ms"""
self.track_no: int = track_number
"""音符所处的音轨"""
self.percussive = (
(is_percussion in MC_PERCUSSION_INSTRUMENT_LIST)
if (is_percussion is None)
else is_percussion
)
self.percussive: bool = is_percussion
"""是否为打击乐器"""
self.extra_info = extra_information
@ -176,7 +366,7 @@ class SingleNote:
@dataclass(init=False)
class SingleCommand:
class MineCommand:
"""存储单个指令的类"""
command_text: str
@ -218,7 +408,7 @@ class SingleCommand:
self.annotation_text = annotation
def copy(self):
return SingleCommand(
return MineCommand(
command=self.command_text,
condition=self.conditional,
tick_delay=self.delay,
@ -399,3 +589,14 @@ NoteChannelType = Mapping[
Dict[int,Dict[int,List[SingleNote,],],]
"""
MineNoteChannelType = Mapping[
int,
List[MineNote,],
]
"""
我的世界频道信息类型
Dict[int,Dict[int,List[MineNote,],],]
"""

View File

@ -18,8 +18,8 @@ Terms & Conditions: License.md in the root directory
import math
import random
from .constants import MM_INSTRUMENT_RANGE_TABLE, MC_INSTRUMENT_BLOCKS_TABLE
from .subclass import SingleNote
from .constants import MC_INSTRUMENT_BLOCKS_TABLE, MM_INSTRUMENT_RANGE_TABLE
from .subclass import SingleNote, MineNote
from .types import (
Any,
@ -74,23 +74,25 @@ def inst_to_sould_with_deviation(
-------
tuple(str我的世界乐器名, int转换算法中的X)
"""
return reference_table.get(
instrumentID,
default=default_instrument,
), 6
return (
reference_table.get(
instrumentID,
default_instrument,
),
6,
)
# 明明已经走了
# 凭什么还要在我心里留下缠绵缱绻
def midi_inst_to_mc_sould(
def midi_inst_to_mc_sound(
instrumentID: int,
reference_table: MidiInstrumentTableType,
default_instrument: str = "note.flute",
) -> str:
"""
返回midi的乐器ID对应的我的世界乐器名对于音域转换算法如下
2**( ( msg.note - 66 ) / 12 ) 即为MC的音高
返回midi的乐器ID对应的我的世界乐器名
Parameters
----------
@ -101,17 +103,13 @@ def midi_inst_to_mc_sould(
Returns
-------
tuple(str我的世界乐器名, int转换算法中的X)
str我的世界乐器名
"""
return reference_table.get(
instrumentID,
default=default_instrument,
default_instrument,
)
# 明明已经走了
# 凭什么还要在我心里留下缠绵缱绻
def natural_curve(
vol: float,
@ -157,44 +155,167 @@ def straight_line(vol: float) -> float:
return vol / -8 + 16
def note_to_command_parameters(
note_: SingleNote,
reference_table: MidiInstrumentTableType,
deviation: int = 0,
volume_percentage: float = 1,
volume_processing_method: Callable[[float], float] = natural_curve,
def minenote_to_command_paramaters(
note_: MineNote,
pitch_deviation: float = 0,
) -> Tuple[
str,
float,
Tuple[float, float, float],
float,
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 reference_table:Dict[int, str] 转换对照表
:param deviation:int 音调偏移量
:param volume_percentage:int 音量占比(0,1]
:param deviation: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,
reference_table,
"note.bd" if note_.percussive else "note.flute",
)
# delaytime_now = round(self.start_time / float(speed) / 50)
mc_pitch = None if note_.percussive else 2 ** ((note_.note - 66 + deviation) / 12)
mc_distance_volume = volume_processing_method(note_.velocity)
mc_distance_volume = volume_processing_method(note_.velocity * volume_percentage)
return mc_sound_ID, mc_distance_volume, volume_percentage, mc_pitch
return (
mc_sound_ID,
(0, mc_distance_volume, 0),
note_.velocity / 127,
None if note_.percussive else 2 ** ((note_.pitch - 66 + deviation) / 12),
)
def from_single_note(
note_: SingleNote, random_select: bool = False, default_block: str = "air"
def midi_msgs_to_minenote(
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"
):
"""
将我的世界乐器名改作音符盒所需的对应方块名称

View File

@ -7,7 +7,7 @@
</img>
</p>
<h3 align="center">一款免费开源的我的世界数字音频</h3>
<h3 align="center">一款免费开源的我的世界数字音频支持</h3>
<p align="center">
<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)
@ -120,7 +120,7 @@ NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
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
[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

View File

@ -32,7 +32,7 @@
## 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)
@ -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
[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

View File

@ -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()
)

View File

@ -80,11 +80,11 @@ else:
# 提示语 检测函数 错误提示语
for args in [
(
f"输入音量:",
f"最小播放音量:",
float,
),
(
f"输入播放速度:",
f"播放速度:",
float,
),
(
@ -118,21 +118,19 @@ else:
if fileFormat == 1
else (
(
(
"结构延展方向:",
lambda a: isin(
a,
{
"z+": ["z+", "Z+"],
"x+": ["X+", "x+"],
"z-": ["Z-", "z-"],
"x-": ["x-", "X-"],
},
),
)
if (playerFormat == 2 and fileFormat == 2)
else ()
),
"结构延展方向:",
lambda a: isin(
a,
{
"z+": ["z+", "Z+"],
"x+": ["X+", "x+"],
"z-": ["Z-", "z-"],
"x-": ["x-", "X-"],
},
),
)
if (playerFormat == 2 and fileFormat == 2)
else ()
)
),
(
@ -152,12 +150,17 @@ else:
),
]:
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} ")
cvt_mid = Musicreater.MidiConvert.from_midi_file(midi_path, old_exe_format=False)
cvt_cfg = ConvertConfig(out_path, *prompts[:3])
cvt_mid = Musicreater.MidiConvert.from_midi_file(
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 playerFormat == 1:

View File

@ -7,7 +7,7 @@ print(
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
input("midi路径:"), old_exe_format=False
),
Musicreater.plugin.ConvertConfig(input("输出路径:"), volume=1),
Musicreater.plugin.ConvertConfig(input("输出路径:"),),
max_height=32,
)
)

View File

@ -15,7 +15,7 @@ setuptools.setup(
version=Musicreater.__version__,
author="Eilles Wan, bgArray",
author_email="TriM-Organization@hotmail.com",
description="一款免费开源的 《我的世界》 mid音乐转换库。\n"
description="一款免费开源的针对《我的世界》音乐的支持库\n"
"A free open-source python library used to convert midi into Minecraft.",
long_description=long_description,
long_description_content_type="text/markdown",