mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-27 11:45:39 +00:00
776 lines
25 KiB
Python
776 lines
25 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
存放主程序所必须的功能性内容
|
||
"""
|
||
|
||
"""
|
||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||
Copyright © 2025 Eilles & bgArray
|
||
|
||
开源相关声明请见 仓库根目录下的 License.md
|
||
Terms & Conditions: License.md in the root directory
|
||
"""
|
||
|
||
# 睿乐组织 开发交流群 861684859
|
||
# Email TriM-Organization@hotmail.com
|
||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||
|
||
import random
|
||
|
||
# from io import BytesIO
|
||
from typing import (
|
||
Any,
|
||
BinaryIO,
|
||
Callable,
|
||
Dict,
|
||
Generator,
|
||
List,
|
||
Literal,
|
||
Optional,
|
||
Tuple,
|
||
Union,
|
||
)
|
||
|
||
from xxhash import xxh3_64, xxh3_128, xxh32
|
||
|
||
from Musicreater.constants import (
|
||
MC_INSTRUMENT_BLOCKS_TABLE,
|
||
MC_PITCHED_INSTRUMENT_LIST,
|
||
MM_INSTRUMENT_DEVIATION_TABLE,
|
||
MM_INSTRUMENT_RANGE_TABLE,
|
||
)
|
||
from Musicreater.exceptions import SingleNoteDecodeError
|
||
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||
|
||
from Musicreater.builtin_plugins.midi_read.utils import midi_inst_to_mc_sound
|
||
|
||
from .subclass import MineNote, mctick2timestr, SingleNoteBox
|
||
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
||
|
||
|
||
def inst_to_sould_with_deviation(
|
||
instrumentID: int,
|
||
reference_table: MidiInstrumentTableType,
|
||
default_instrument: str = "note.flute",
|
||
) -> Tuple[str, int]:
|
||
"""
|
||
返回midi的乐器ID对应的我的世界乐器名,对于音域转换算法,如下:
|
||
2**( ( msg.note - 60 - X ) / 12 ) 即为MC的音高
|
||
|
||
Parameters
|
||
----------
|
||
instrumentID: int
|
||
midi的乐器ID
|
||
reference_table: Dict[int, Tuple[str, int]]
|
||
转换乐器参照表
|
||
default_instrument: str
|
||
查无此乐器时的替换乐器
|
||
|
||
Returns
|
||
-------
|
||
tuple(str我的世界乐器名, int转换算法中的偏移量)
|
||
"""
|
||
sound_id = midi_inst_to_mc_sound(
|
||
instrumentID=instrumentID,
|
||
reference_table=reference_table,
|
||
default_instrument=default_instrument,
|
||
)
|
||
return sound_id, MM_INSTRUMENT_DEVIATION_TABLE.get(
|
||
sound_id,
|
||
MM_INSTRUMENT_DEVIATION_TABLE.get(
|
||
default_instrument, 6 if sound_id in MC_PITCHED_INSTRUMENT_LIST else -1
|
||
),
|
||
)
|
||
|
||
|
||
|
||
|
||
|
||
def midi_msgs_to_minenote_using_kami_respack(
|
||
inst_: int, # 乐器编号
|
||
note_: int,
|
||
percussive_: bool, # 是否作为打击乐器启用
|
||
volume_: int,
|
||
velocity_: int,
|
||
panning_: int,
|
||
start_time_: int,
|
||
duration_: int,
|
||
play_speed: float,
|
||
midi_reference_table: MidiInstrumentTableType,
|
||
volume_processing_method_: Callable[[float], float],
|
||
panning_processing_method_: FittingFunctionType,
|
||
note_table_replacement: Dict[str, str] = {},
|
||
lyric_line: str = "",
|
||
) -> MineNote:
|
||
"""
|
||
将Midi信息转为我的世界音符对象,使用神羽资源包兼容格式
|
||
|
||
Parameters
|
||
------------
|
||
inst_: int
|
||
乐器编号
|
||
note_: int
|
||
音高编号(音符编号)
|
||
percussive_: bool
|
||
是否作为打击乐器启用
|
||
volume_: int
|
||
音量
|
||
velocity_: int
|
||
力度
|
||
panning_: int
|
||
声相偏移
|
||
start_time_: int
|
||
音符起始时间(微秒)
|
||
duration_: int
|
||
音符持续时间(微秒)
|
||
play_speed: float
|
||
曲目播放速度
|
||
midi_reference_table: Dict[int, str]
|
||
转换对照表
|
||
volume_processing_method_: Callable[[float], float]
|
||
音量处理函数
|
||
panning_processing_method_: Callable[[float], float]
|
||
立体声相偏移处理函数
|
||
note_table_replacement: Dict[str, str]
|
||
音符替换表,定义 Minecraft 音符字串的替换
|
||
lyric_line: str
|
||
该音符的歌词
|
||
|
||
Returns
|
||
---------
|
||
MineNote
|
||
我的世界音符对象
|
||
"""
|
||
|
||
using_original = False
|
||
if not percussive_ and (0 <= inst_ <= 119):
|
||
mc_sound_ID = "{}{}.{}".format(
|
||
# inst_, "c" if (duration_ > 1000_000) and (inst_ in (0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53)) else "d", note_
|
||
inst_,
|
||
"d",
|
||
note_,
|
||
)
|
||
elif percussive_ and (27 <= inst_ <= 87):
|
||
mc_sound_ID = "-1d.{}".format(inst_)
|
||
else:
|
||
using_original = True
|
||
mc_sound_ID = midi_inst_to_mc_sound(
|
||
inst_,
|
||
midi_reference_table,
|
||
"note.bd" if percussive_ else "note.flute",
|
||
)
|
||
|
||
return MineNote(
|
||
mc_sound_name=note_table_replacement.get(mc_sound_ID, mc_sound_ID),
|
||
midi_pitch=note_ if using_original else 1,
|
||
midi_velocity=velocity_,
|
||
start_time=(tk := int(start_time_ / float(play_speed) / 50000)),
|
||
last_time=round(duration_ / float(play_speed) / 50000),
|
||
mass_precision_time=round((start_time_ / float(play_speed) - tk * 50000) / 800),
|
||
is_percussion=percussive_,
|
||
distance=volume_processing_method_(volume_),
|
||
azimuth=(panning_processing_method_(panning_), 0),
|
||
extra_information={
|
||
"USING_ORIGINAL_SOUND": using_original, # 判断 extra_information 中是否有 USING_ORIGINAL_SOUND 键是判断是否使用神羽资源包解析的一个显著方法
|
||
"INST_VALUE": note_ if percussive_ else inst_,
|
||
"NOTE_VALUE": inst_ if percussive_ else note_,
|
||
"LYRIC_TEXT": lyric_line,
|
||
"VOLUME_VALUE": volume_,
|
||
"PIN_VALUE": panning_,
|
||
},
|
||
)
|
||
|
||
|
||
# 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_name=mc_sound_ID,
|
||
# midi_pitch=note_.pitch,
|
||
# midi_velocity=note_.velocity,
|
||
# start_time=round(note_.start_time / float(play_speed) / 50),
|
||
# last_time=round(note_.duration / float(play_speed) / 50),
|
||
# is_percussion=note_.percussive,
|
||
# displacement=(0, mc_distance_volume, 0),
|
||
# extra_information=note_.extra_info,
|
||
# )
|
||
|
||
|
||
def is_in_diapason(note_pitch: float, 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:
|
||
"""一个 MineNote 音符是否在其乐器可演奏之范围之内"""
|
||
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"
|
||
):
|
||
"""
|
||
将我的世界乐器名改作音符盒所需的对应方块名称
|
||
|
||
Parameters
|
||
----------
|
||
note_: MineNote
|
||
音符类
|
||
random_select: bool
|
||
是否随机选取对应方块
|
||
default_block: str
|
||
查表查不到怎么办?默认一个!
|
||
|
||
Returns
|
||
-------
|
||
str方块名称
|
||
"""
|
||
pass
|
||
# return SingleNoteBox() # TO-DO
|
||
|
||
|
||
def soundID_to_blockID(
|
||
sound_id: str, random_select: bool = False, default_block: str = "air"
|
||
) -> str:
|
||
"""
|
||
将我的世界乐器名改作音符盒所需的对应方块名称
|
||
|
||
Parameters
|
||
----------
|
||
sound_id: str
|
||
将我的世界乐器名
|
||
random_select: bool
|
||
是否随机选取对应方块
|
||
default_block: str
|
||
查表查不到怎么办?默认一个!
|
||
|
||
Returns
|
||
-------
|
||
str方块名称
|
||
"""
|
||
if random_select:
|
||
return random.choice(MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,)))
|
||
else:
|
||
return MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,))[0]
|
||
|
||
|
||
def load_decode_musicsequence_metainfo(
|
||
buffer_in: BinaryIO,
|
||
) -> Tuple[str, float, float, bool, int, bool]:
|
||
"""
|
||
以流的方式解码音乐序列元信息
|
||
|
||
Parameters
|
||
----------
|
||
buffer_in: BytesIO
|
||
MSQ格式的字节流
|
||
|
||
Returns
|
||
-------
|
||
Tuple[str, float, float, bool, int]
|
||
音乐名称,最小音量,音调偏移,是否启用高精度,最后的流指针位置,是否使用新的音符存储格式(MineNote第三版)
|
||
|
||
"""
|
||
note_format_v3 = buffer_in.read(4) in (b"MSQ$", b"FSQ$")
|
||
group_1 = int.from_bytes(buffer_in.read(2), "big")
|
||
group_2 = int.from_bytes(buffer_in.read(2), "big", signed=False)
|
||
|
||
# high_quantity = bool(group_2 & 0b1000000000000000)
|
||
# print(group_2, high_quantity)
|
||
|
||
music_name_ = buffer_in.read(stt_index := (group_1 >> 10)).decode("GB18030")
|
||
|
||
return (
|
||
music_name_,
|
||
(group_1 & 0b1111111111) / 1000,
|
||
(
|
||
(-1 if group_2 & 0b100000000000000 else 1)
|
||
* (group_2 & 0b11111111111111)
|
||
/ 1000
|
||
),
|
||
bool(group_2 & 0b1000000000000000),
|
||
stt_index + 8,
|
||
note_format_v3,
|
||
)
|
||
|
||
|
||
def load_decode_fsq_flush_release(
|
||
buffer_in: BinaryIO,
|
||
starter_index: int,
|
||
high_quantity_note: bool,
|
||
new_note_format: bool,
|
||
) -> Generator[MineNote, Any, None]:
|
||
"""
|
||
以流的方式解码FSQ音乐序列的音符序列并流式返回
|
||
|
||
Parameters
|
||
----------
|
||
buffer_in : BytesIO
|
||
输入的MSQ格式二进制字节流
|
||
starter_index : int
|
||
字节流中,音符序列的起始索引
|
||
high_quantity_note : bool
|
||
是否启用高精度音符解析
|
||
new_note_format : bool
|
||
是否启用新音符格式解析(MineNote第三版)
|
||
|
||
Returns
|
||
-------
|
||
Generator[MineNote, Any, None]
|
||
以流的方式返回解码后的音符序列,每次返回一个元组
|
||
元组中包含两个元素,第一个元素为音符所在通道的索引,第二个元素为音符对象
|
||
|
||
Raises
|
||
------
|
||
MusicSequenceDecodeError
|
||
当解码过程中出现错误,抛出异常
|
||
"""
|
||
|
||
if buffer_in.tell() != starter_index:
|
||
buffer_in.seek(starter_index, 0)
|
||
|
||
total_note_count = int.from_bytes(
|
||
buffer_in.read(5),
|
||
"big",
|
||
signed=False,
|
||
)
|
||
|
||
for i in range(total_note_count):
|
||
if (i % 100 == 0) and i:
|
||
buffer_in.read(4)
|
||
|
||
try:
|
||
_note_bytes_length = (
|
||
12 + high_quantity_note + ((_first_byte := (buffer_in.read(1)))[0] >> 2)
|
||
)
|
||
|
||
yield (
|
||
MineNote.decode(
|
||
code_buffer=_first_byte + buffer_in.read(_note_bytes_length),
|
||
is_high_time_precision=high_quantity_note,
|
||
)
|
||
if new_note_format
|
||
else decode_note_bytes_v2(
|
||
code_buffer_bytes=_first_byte + buffer_in.read(_note_bytes_length),
|
||
is_high_time_precision=high_quantity_note,
|
||
)
|
||
)
|
||
except Exception as _err:
|
||
# print(bytes_buffer_in[stt_index:end_index])
|
||
raise SingleNoteDecodeError(
|
||
"所截取的音符码之首个字节:",
|
||
_first_byte,
|
||
) from _err
|
||
|
||
|
||
def load_decode_msq_flush_release(
|
||
buffer_in: BinaryIO,
|
||
starter_index: int,
|
||
high_quantity_note: bool,
|
||
new_note_format: bool,
|
||
) -> Generator[Tuple[int, MineNote], Any, None]:
|
||
"""以流的方式解码MSQ音乐序列的音符序列并流式返回
|
||
|
||
Parameters
|
||
----------
|
||
buffer_in : BytesIO
|
||
输入的MSQ格式二进制字节流
|
||
starter_index : int
|
||
字节流中,音符序列的起始索引
|
||
high_quantity_note : bool
|
||
是否启用高精度音符解析
|
||
new_note_format : bool
|
||
是否启用新音符格式解析(MineNote第三版)
|
||
|
||
Returns
|
||
-------
|
||
Generator[Tuple[int, MineNote], Any, None]
|
||
以流的方式返回解码后的音符序列,每次返回一个元组
|
||
元组中包含两个元素,第一个元素为音符所在通道的索引,第二个元素为音符对象
|
||
|
||
Raises
|
||
------
|
||
MusicSequenceDecodeError
|
||
当解码过程中出现错误,抛出异常
|
||
|
||
"""
|
||
|
||
# _total_verify = xxh3_64(buffer_in.read(starter_index), seed=total_note_count)
|
||
|
||
# buffer_in.seek(starter_index, 0)
|
||
if buffer_in.tell() != starter_index:
|
||
buffer_in.seek(starter_index, 0)
|
||
_bytes_buffer_in = buffer_in.read()
|
||
# int.from_bytes(_bytes_buffer_in[0 : 4], "big")
|
||
|
||
_now_channel_starter_index = 0
|
||
|
||
_total_note_count = 1
|
||
|
||
_channel_infos = enumerated_stuffcopy_dictionary(
|
||
staff={"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
|
||
)
|
||
|
||
for __channel_index in _channel_infos.keys():
|
||
# _channel_note_count = 0
|
||
|
||
_now_channel_ender_sign = xxh3_64(
|
||
_bytes_buffer_in[
|
||
_now_channel_starter_index : _now_channel_starter_index + 4
|
||
],
|
||
seed=3,
|
||
).digest()
|
||
|
||
# print(
|
||
# "[DEBUG] 索引取得:",
|
||
# _bytes_buffer_in[
|
||
# _now_channel_starter_index : _now_channel_starter_index + 4
|
||
# ],
|
||
# "校验索引",
|
||
# _now_channel_ender_sign,
|
||
# )
|
||
|
||
_now_channel_ender_index = _bytes_buffer_in.find(_now_channel_ender_sign)
|
||
|
||
# print("[DEBUG] 索引取得:", _now_channel_ender_index,)
|
||
|
||
_channel_note_count = int.from_bytes(
|
||
_bytes_buffer_in[
|
||
_now_channel_starter_index : _now_channel_starter_index + 4
|
||
],
|
||
"big",
|
||
)
|
||
|
||
if _channel_note_count == 0:
|
||
continue
|
||
|
||
while (
|
||
xxh3_64(
|
||
_bytes_buffer_in[_now_channel_starter_index:_now_channel_ender_index],
|
||
seed=_channel_note_count,
|
||
).digest()
|
||
!= _bytes_buffer_in[
|
||
_now_channel_ender_index + 8 : _now_channel_ender_index + 16
|
||
]
|
||
):
|
||
_now_channel_ender_index += 8 + _bytes_buffer_in[
|
||
_now_channel_ender_index + 8 :
|
||
].find(_now_channel_ender_sign)
|
||
|
||
# print(
|
||
# "[WARNING] XXHASH 无法匹配,当前序列",
|
||
# __channel_index,
|
||
# "当前全部序列字节串",
|
||
# _bytes_buffer_in[
|
||
# _now_channel_starter_index:_now_channel_ender_index
|
||
# ],
|
||
# "校验值",
|
||
# xxh3_64(
|
||
# _bytes_buffer_in[
|
||
# _now_channel_starter_index:_now_channel_ender_index
|
||
# ],
|
||
# seed=_channel_note_count,
|
||
# ).digest(),
|
||
# _bytes_buffer_in[
|
||
# _now_channel_ender_index + 8 : _now_channel_ender_index + 16
|
||
# ],
|
||
# "改变结尾索引",
|
||
# _now_channel_ender_index,
|
||
# )
|
||
|
||
_channel_infos[__channel_index]["NOW_INDEX"] = _now_channel_starter_index + 4
|
||
_channel_infos[__channel_index]["END_INDEX"] = _now_channel_ender_index
|
||
_channel_infos[__channel_index]["NOTE_COUNT"] = _channel_note_count
|
||
|
||
# print(
|
||
# "[DEBUG] 当前序列", __channel_index, "值", _channel_infos[__channel_index]
|
||
# )
|
||
|
||
_total_note_count += _channel_note_count
|
||
|
||
_now_channel_starter_index = _now_channel_ender_index + 16
|
||
# for i in range(
|
||
# int.from_bytes(
|
||
# bytes_buffer_in[stt_index : (stt_index := stt_index + 4)], "big"
|
||
# )
|
||
# ):
|
||
_to_yield_note_list: List[Tuple[MineNote, int]] = []
|
||
|
||
# {"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
|
||
|
||
while _total_note_count:
|
||
_read_in_note_list: List[Tuple[MineNote, int]] = []
|
||
for __channel_index in _channel_infos.keys():
|
||
if (
|
||
_channel_infos[__channel_index]["HAVE_READ"]
|
||
< _channel_infos[__channel_index]["NOTE_COUNT"]
|
||
):
|
||
# print("当前已读", _channel_infos[__channel_index]["HAVE_READ"])
|
||
try:
|
||
_end_index = (
|
||
(_stt_index := _channel_infos[__channel_index]["NOW_INDEX"])
|
||
+ 13
|
||
+ high_quantity_note
|
||
+ (_bytes_buffer_in[_stt_index] >> 2)
|
||
)
|
||
# print("读取音符字节串", _bytes_buffer_in[_stt_index:_end_index])
|
||
_read_in_note_list.append(
|
||
(
|
||
(
|
||
MineNote.decode(
|
||
code_buffer=_bytes_buffer_in[_stt_index:_end_index],
|
||
is_high_time_precision=high_quantity_note,
|
||
)
|
||
if new_note_format
|
||
else decode_note_bytes_v2(
|
||
code_buffer_bytes=_bytes_buffer_in[
|
||
_stt_index:_end_index
|
||
],
|
||
is_high_time_precision=high_quantity_note,
|
||
)
|
||
),
|
||
__channel_index,
|
||
)
|
||
)
|
||
_channel_infos[__channel_index]["HAVE_READ"] += 1
|
||
_channel_infos[__channel_index]["NOW_INDEX"] = _end_index
|
||
_total_note_count -= 1
|
||
except Exception as _err:
|
||
# print(channels_)
|
||
raise SingleNoteDecodeError("难以定位的解码错误") from _err
|
||
if not _read_in_note_list:
|
||
break
|
||
# _note_list.append
|
||
min_stt_note: MineNote = min(_read_in_note_list, key=lambda x: x[0].start_tick)[
|
||
0
|
||
]
|
||
for i in range(len(_to_yield_note_list)):
|
||
__note, __channel_index = _to_yield_note_list[i]
|
||
if __note.start_tick >= min_stt_note.start_tick:
|
||
break
|
||
else:
|
||
yield __channel_index, __note
|
||
_to_yield_note_list.pop(i)
|
||
|
||
_to_yield_note_list.extend(_read_in_note_list)
|
||
_to_yield_note_list.sort(key=lambda x: x[0].start_tick)
|
||
|
||
for __note, __channel_index in sorted(
|
||
_to_yield_note_list, key=lambda x: x[0].start_tick
|
||
):
|
||
yield __channel_index, __note
|
||
# 俺寻思能用
|
||
|
||
|
||
def guess_deviation(
|
||
total_note_count: int,
|
||
total_instrument_count: int,
|
||
note_count_per_instrument: Optional[Dict[str, int]] = None,
|
||
qualified_note_count_per_instrument: Optional[Dict[str, int]] = None,
|
||
music_channels: Optional[MineNoteChannelType] = None,
|
||
) -> float:
|
||
"""
|
||
通过乐器权重来计算一首歌的音调偏移
|
||
这个方法未经验证,但理论有效,金羿首创
|
||
|
||
Parameters
|
||
----------
|
||
total_note_count: int
|
||
歌曲总音符数
|
||
total_instrument_count: int
|
||
歌曲乐器总数
|
||
note_count_per_instrument: Dict[str, int]
|
||
乐器名称与乐器音符数对照表
|
||
qualified_note_count_per_instrument: Dict[str, int]
|
||
每个乐器中,符合该乐器的音调范围的音符数
|
||
music_channels: MineNoteChannelType
|
||
MusicSequence类的音乐通道字典
|
||
|
||
Returns
|
||
-------
|
||
float估测的音调偏移值
|
||
"""
|
||
if note_count_per_instrument is None or qualified_note_count_per_instrument is None:
|
||
if music_channels is None:
|
||
raise ValueError("参数不足,算逑!")
|
||
note_count_per_instrument = {}
|
||
qualified_note_count_per_instrument = {}
|
||
for this_note in [k for j in music_channels.values() for k in j]:
|
||
if this_note.sound_name in note_count_per_instrument.keys():
|
||
note_count_per_instrument[this_note.sound_name] += 1
|
||
qualified_note_count_per_instrument[
|
||
this_note.sound_name
|
||
] += is_note_in_diapason(this_note)
|
||
else:
|
||
note_count_per_instrument[this_note.sound_name] = 1
|
||
qualified_note_count_per_instrument[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_instrument[inst])
|
||
)
|
||
for inst, note_count in note_count_per_instrument.items()
|
||
]
|
||
)
|
||
/ total_instrument_count
|
||
/ total_note_count
|
||
)
|
||
|
||
|
||
# 延长支持用
|
||
|
||
|
||
def decode_note_bytes_v1(
|
||
code_buffer_bytes: bytes,
|
||
) -> MineNote:
|
||
"""使用第一版的 MineNote 字节码标准析出MineNote类"""
|
||
group_1 = int.from_bytes(code_buffer_bytes[: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_bytes[6] & 0b1:
|
||
position_displacement_ = (
|
||
int.from_bytes(
|
||
code_buffer_bytes[8 + sound_name_length : 10 + sound_name_length],
|
||
"big",
|
||
)
|
||
/ 1000,
|
||
int.from_bytes(
|
||
code_buffer_bytes[10 + sound_name_length : 12 + sound_name_length],
|
||
"big",
|
||
)
|
||
/ 1000,
|
||
int.from_bytes(
|
||
code_buffer_bytes[12 + sound_name_length : 14 + sound_name_length],
|
||
"big",
|
||
)
|
||
/ 1000,
|
||
)
|
||
else:
|
||
position_displacement_ = (0, 0, 0)
|
||
|
||
try:
|
||
return MineNote.from_traditional(
|
||
mc_sound_name=code_buffer_bytes[8 : 8 + sound_name_length].decode(
|
||
encoding="utf-8"
|
||
),
|
||
midi_pitch=note_pitch_,
|
||
midi_velocity=code_buffer_bytes[6] >> 1,
|
||
start_time=start_tick_,
|
||
last_time=duration_,
|
||
is_percussion=percussive_,
|
||
displacement=position_displacement_,
|
||
extra_information={"track_number": code_buffer_bytes[7]},
|
||
)
|
||
except:
|
||
print(code_buffer_bytes, "\n", code_buffer_bytes[8 : 8 + sound_name_length])
|
||
raise
|
||
|
||
|
||
def decode_note_bytes_v2(
|
||
code_buffer_bytes: bytes, is_high_time_precision: bool = True
|
||
) -> MineNote:
|
||
"""使用第二版的 MineNote 字节码标准析出MineNote类"""
|
||
group_1 = int.from_bytes(code_buffer_bytes[: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_bytes[6] & 0b1:
|
||
position_displacement_ = (
|
||
int.from_bytes(
|
||
(
|
||
code_buffer_bytes[8 + sound_name_length : 10 + sound_name_length]
|
||
if is_high_time_precision
|
||
else code_buffer_bytes[
|
||
7 + sound_name_length : 9 + sound_name_length
|
||
]
|
||
),
|
||
"big",
|
||
)
|
||
/ 1000,
|
||
int.from_bytes(
|
||
(
|
||
code_buffer_bytes[10 + sound_name_length : 12 + sound_name_length]
|
||
if is_high_time_precision
|
||
else code_buffer_bytes[
|
||
9 + sound_name_length : 11 + sound_name_length
|
||
]
|
||
),
|
||
"big",
|
||
)
|
||
/ 1000,
|
||
int.from_bytes(
|
||
(
|
||
code_buffer_bytes[12 + sound_name_length : 14 + sound_name_length]
|
||
if is_high_time_precision
|
||
else code_buffer_bytes[
|
||
11 + sound_name_length : 13 + sound_name_length
|
||
]
|
||
),
|
||
"big",
|
||
)
|
||
/ 1000,
|
||
)
|
||
else:
|
||
position_displacement_ = (0, 0, 0)
|
||
|
||
try:
|
||
return MineNote.from_traditional(
|
||
mc_sound_name=(
|
||
o := (
|
||
code_buffer_bytes[8 : 8 + sound_name_length]
|
||
if is_high_time_precision
|
||
else code_buffer_bytes[7 : 7 + sound_name_length]
|
||
)
|
||
).decode(encoding="GB18030"),
|
||
midi_pitch=note_pitch_,
|
||
midi_velocity=code_buffer_bytes[6] >> 1,
|
||
start_time=start_tick_,
|
||
last_time=duration_,
|
||
mass_precision_time=code_buffer_bytes[7] if is_high_time_precision else 0,
|
||
is_percussion=percussive_,
|
||
displacement=position_displacement_,
|
||
)
|
||
except:
|
||
print(code_buffer_bytes, "\n", o)
|
||
raise
|