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
|
||||
|
||||
|
||||
__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", 未来功能
|
||||
# 默认值
|
||||
|
@ -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),
|
||||
}
|
||||
"""不同乐器的音域偏离对照表"""
|
||||
|
||||
|
@ -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)
|
@ -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):
|
||||
|
@ -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,
|
||||
)
|
||||
that_note := midi_msgs_to_minenote(
|
||||
inst_=(
|
||||
msg.note
|
||||
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,
|
||||
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(
|
||||
+ r"playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
||||
mc_sound_ID,
|
||||
mc_distance_volume,
|
||||
*relative_coordinates,
|
||||
volume_percentage,
|
||||
mc_pitch,
|
||||
)
|
||||
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(
|
||||
+ r"playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
||||
mc_sound_ID,
|
||||
mc_distance_volume,
|
||||
*relative_coordinates,
|
||||
volume_percentage,
|
||||
mc_pitch,
|
||||
)
|
||||
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
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
):
|
||||
"""
|
||||
|
@ -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"))
|
||||
|
@ -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 的参数和自定义参数的判断
|
||||
# 改这一段没🐎
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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):
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
||||
# 你以为写完了吗?其实并没有
|
||||
|
@ -15,4 +15,4 @@ Terms & Conditions: License.md in the root directory
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
import nbtschematic
|
||||
# import nbtschematic
|
||||
|
@ -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)
|
@ -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,],],]
|
||||
"""
|
||||
|
@ -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(
|
||||
return (
|
||||
reference_table.get(
|
||||
instrumentID,
|
||||
default=default_instrument,
|
||||
), 6
|
||||
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"
|
||||
):
|
||||
"""
|
||||
将我的世界乐器名改作音符盒所需的对应方块名称
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
)
|
15
example.py
15
example.py
@ -80,11 +80,11 @@ else:
|
||||
# 提示语 检测函数 错误提示语
|
||||
for args in [
|
||||
(
|
||||
f"输入音量:",
|
||||
f"最小播放音量:",
|
||||
float,
|
||||
),
|
||||
(
|
||||
f"输入播放速度:",
|
||||
f"播放速度:",
|
||||
float,
|
||||
),
|
||||
(
|
||||
@ -117,7 +117,6 @@ else:
|
||||
)
|
||||
if fileFormat == 1
|
||||
else (
|
||||
(
|
||||
(
|
||||
"结构延展方向:",
|
||||
lambda a: isin(
|
||||
@ -132,7 +131,6 @@ else:
|
||||
)
|
||||
if (playerFormat == 2 and fileFormat == 2)
|
||||
else ()
|
||||
),
|
||||
)
|
||||
),
|
||||
(
|
||||
@ -152,12 +150,17 @@ else:
|
||||
),
|
||||
]:
|
||||
if args:
|
||||
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:
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
2
setup.py
2
setup.py
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user