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