MSQ 流式适配与校验增强,新增 NBS 音色表

This commit is contained in:
EillesWan 2025-04-08 17:49:49 +08:00
parent 889f8f9641
commit c14489f3a7
13 changed files with 629 additions and 75 deletions

View File

@ -22,8 +22,8 @@ The Licensor of Musicreater("this project") is Eilles Wan, bgArray.
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__version__ = "2.2.3" __version__ = "2.2.4"
__vername__ = "Java版欲适配、初步MSQ流式适配" __vername__ = "MSQ 流式适配与校验增强,新增 NBS 音色表"
__author__ = ( __author__ = (
("金羿", "Eilles"), ("金羿", "Eilles"),
("诸葛亮与八卦阵", "bgArray"), ("诸葛亮与八卦阵", "bgArray"),
@ -48,6 +48,14 @@ __all__ = [
"MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE", "MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE",
"MM_TOUCH_PITCHED_INSTRUMENT_TABLE", "MM_TOUCH_PITCHED_INSTRUMENT_TABLE",
"MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE", "MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE",
"MM_NBS_PITCHED_INSTRUMENT_TABLE",
"MM_NBS_PERCUSSION_INSTRUMENT_TABLE",
# 操作性函数
"natural_curve",
"straight_line",
"load_decode_msq_metainfo",
"load_decode_msq_flush_release",
"guess_deviation",
] ]
from .main import * from .main import *

View File

@ -16,7 +16,8 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com # Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from .types import Dict, List, Tuple, MidiInstrumentTableType, MidiNoteNameTableType # from .types import Dict, List, Tuple, MidiInstrumentTableType, MidiNoteNameTableType
from typing import Dict, List, Tuple
x = "x" x = "x"
""" """
@ -478,7 +479,12 @@ MM_INSTRUMENT_DEVIATION_TABLE: Dict[str, int] = {
"fire.ignite": 0, "fire.ignite": 0,
"note.cow_bell": 6, "note.cow_bell": 6,
} }
"""不同乐器的音调偏离对照表""" """
不同乐器的音调偏离对照表
*注意* 该表中的单位是对于 Midi Pitch 音调整数的低音偏移
也就是说该数值越高则在 Midi Pitch 中的值域越低
默认的偏移量为 6 因为在计算音高时候少减去了 6 Pitch 单位
"""
# Midi乐器对MC乐器对照表 # Midi乐器对MC乐器对照表
@ -853,6 +859,8 @@ MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
"""“偷吃”打击乐器对照表""" """“偷吃”打击乐器对照表"""
# Dislink “断联” 音色对照表 # Dislink “断联” 音色对照表
# https://github.com/Dislink/midi2bdx/blob/main/index.html
MM_DISLINK_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = { MM_DISLINK_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
0: "note.harp", 0: "note.harp",
@ -1037,6 +1045,210 @@ MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
} }
"""“断联”打击乐器对照表""" """“断联”打击乐器对照表"""
# NoteBlockStudio “NBS”音色对照表
# https://github.com/OpenNBS/NoteBlockStudio/blob/main/scripts/midi_instruments/midi_instruments.gml
MM_NBS_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
0: "note.harp",
1: "note.pling",
2: "note.harp",
3: "note.pling",
4: "note.harp",
5: "note.harp",
6: "note.guitar",
7: "note.banjo",
8: "note.bell",
9: "note.bell",
10: "note.bell",
11: "note.iron_xylophone",
12: "note.iron_xylophone",
13: "note.xylophone",
14: "note.bell",
15: "note.iron_xylophone",
16: "note.flute",
17: "note.flute",
18: "note.flute",
19: "note.flute",
20: "note.flute",
21: "note.flute",
22: "note.flute",
23: "note.flute",
24: "note.guitar",
25: "note.guitar",
26: "note.guitar",
27: "note.bass",
28: "note.guitar",
29: "note.guitar",
30: "note.bass",
31: "note.bass",
32: "note.bass",
33: "note.guitar",
34: "note.guitar",
35: "note.bass",
36: "note.pling",
37: "note.flute",
38: "note.flute",
39: "note.flute",
40: "note.flute",
41: "note.flute",
42: "note.didgeridoo",
43: "note.flute",
44: "note.didgeridoo",
45: "note.flute",
46: "note.flute",
47: "note.flute",
48: "note.flute",
49: "note.flute",
50: "note.flute",
51: "note.flute",
52: "note.flute",
53: "note.flute",
54: "note.flute",
55: "note.flute",
56: "note.flute",
57: "note.flute",
58: "note.flute",
59: "note.flute",
60: "note.bit",
61: "note.flute",
62: "note.flute",
63: "note.flute",
64: "note.flute",
65: "note.guitar",
66: "note.flute",
67: "note.flute",
68: "note.flute",
69: "note.bell",
70: "note.flute",
71: "note.flute",
72: "note.flute",
73: "note.flute",
74: "note.chime",
75: "note.flute",
76: "note.flute",
77: "note.guitar",
78: "note.pling",
79: "note.flute",
80: "note.guitar",
81: "note.banjo",
82: "note.banjo",
83: "note.banjo",
84: "note.guitar",
85: "note.iron_xylophone",
86: "note.flute",
87: "note.flute",
88: "note.chime",
89: "note.cow_bell",
90: "note.iron_xylophone",
91: "note.xylophone",
92: "note.basedrum",
93: "note.snare",
94: "note.snare",
95: "note.basedrum",
96: "note.snare",
97: "note.hat",
98: "note.snare",
99: "note.hat",
100: "note.basedrum",
101: "note.hat",
102: "note.basedrum",
103: "note.hat",
104: "note.basedrum",
105: "note.snare",
106: "note.snare",
107: "note.snare",
108: "note.cow_bell",
109: "note.snare",
110: "note.hat",
111: "note.snare",
112: "note.hat",
113: "note.hat",
114: "note.hat",
115: "note.hat",
116: "note.hat",
117: "note.chime",
118: "note.hat",
119: "note.snare",
120: "note.hat",
121: "note.hat",
122: "note.hat",
123: "note.hat",
124: "note.hat",
125: "note.snare",
126: "note.basedrum",
127: "note.basedrum",
}
"""“NBS”乐音乐器对照表"""
MM_NBS_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
24: "note.bit",
25: "note.snare",
26: "note.hat",
27: "note.snare",
28: "note.snare",
29: "note.hat",
30: "note.hat",
31: "note.hat",
32: "note.hat",
33: "note.hat",
34: "note.chime",
35: "note.basedrum",
36: "note.basedrum",
37: "note.hat",
38: "note.snare",
39: "note.hat",
40: "note.snare",
41: "note.basedrum",
42: "note.snare",
43: "note.basedrum",
44: "note.snare",
45: "note.basedrum",
46: "note.basedrum",
47: "note.snare",
48: "note.snare",
49: "note.snare",
50: "note.snare",
51: "note.snare",
52: "note.snare",
53: "note.hat",
54: "note.snare",
55: "note.snare",
56: "note.cow_bell",
57: "note.snare",
58: "note.hat",
59: "note.snare",
60: "note.hat",
61: "note.hat",
62: "note.hat",
63: "note.basedrum",
64: "note.basedrum",
65: "note.snare",
66: "note.snare",
67: "note.xylophone",
68: "note.xylophone",
69: "note.hat",
70: "note.hat",
71: "note.flute",
72: "note.flute",
73: "note.hat",
74: "note.hat",
75: "note.hat",
76: "note.hat",
77: "note.hat",
78: "note.didgeridoo",
79: "note.didgeridoo",
80: "note.hat",
81: "note.chime",
82: "note.hat",
83: "note.chime",
84: "note.chime",
85: "note.hat",
86: "note.basedrum",
87: "note.basedrum",
}
"""“NBS”打击乐器对照表"""
# Midi音高对MC方块对照表 # Midi音高对MC方块对照表
# 金羿ELS 音符方块对照表 # 金羿ELS 音符方块对照表

View File

@ -34,7 +34,6 @@ import os
import math import math
import mido import mido
from xxhash import xxh3_64, xxh3_128
from .constants import * from .constants import *
from .exceptions import * from .exceptions import *
@ -169,6 +168,7 @@ class MusicSequence:
minimum_vol: float = 0.1, minimum_vol: float = 0.1,
volume_processing_function: FittingFunctionType = natural_curve, volume_processing_function: FittingFunctionType = natural_curve,
deviation: float = 0, deviation: float = 0,
note_referance_table_replacement: Dict[str, str] = {},
): ):
""" """
自mido对象导入一个音符序列类 自mido对象导入一个音符序列类
@ -210,6 +210,7 @@ class MusicSequence:
default_tempo_value=default_tempo, default_tempo_value=default_tempo,
vol_processing_function=volume_processing_function, vol_processing_function=volume_processing_function,
ignore_mismatch_error=mismatch_error_ignorance, ignore_mismatch_error=mismatch_error_ignorance,
note_rtable_replacement=note_referance_table_replacement,
) )
else: else:
note_channels = {} note_channels = {}
@ -241,6 +242,7 @@ class MusicSequence:
music_name_ = bytes_buffer_in[8 : (stt_index := 8 + (group_1 >> 10))].decode( music_name_ = bytes_buffer_in[8 : (stt_index := 8 + (group_1 >> 10))].decode(
"GB18030" "GB18030"
) )
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[]) channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
total_note_count = 0 total_note_count = 0
if verify: if verify:
@ -477,8 +479,11 @@ class MusicSequence:
"""重命名此音乐""" """重命名此音乐"""
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 = True):
"""在指定通道添加一个音符""" """
在指定通道添加一个音符
值得注意在版本 2.2.3 及之前 is_sort 参数默认为 False 在此之后为 True
"""
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(): if note.sound_name in self.note_count_per_instrument.keys():
@ -498,6 +503,7 @@ class MusicSequence:
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,
vol_processing_function: FittingFunctionType = natural_curve, vol_processing_function: FittingFunctionType = natural_curve,
note_rtable_replacement: Dict[str, str] = {},
) -> Tuple[MineNoteChannelType, int, Dict[str, int]]: ) -> Tuple[MineNoteChannelType, int, Dict[str, int]]:
""" """
将midi解析并转换为频道音符字典 将midi解析并转换为频道音符字典
@ -616,6 +622,7 @@ class MusicSequence:
else pitched_note_rtable else pitched_note_rtable
), ),
volume_processing_method_=vol_processing_function, volume_processing_method_=vol_processing_function,
note_table_replacement=note_rtable_replacement,
) )
) )
note_count += 1 note_count += 1
@ -693,6 +700,7 @@ class MidiConvert(MusicSequence):
enable_old_exe_format: bool = False, enable_old_exe_format: bool = False,
minimum_volume: float = 0.1, minimum_volume: float = 0.1,
vol_processing_function: FittingFunctionType = natural_curve, vol_processing_function: FittingFunctionType = natural_curve,
note_rtable_replacement: Dict[str, str] = {},
): ):
""" """
简单的midi转换类将midi对象转换为我的世界结构或者包 简单的midi转换类将midi对象转换为我的世界结构或者包
@ -742,6 +750,7 @@ class MidiConvert(MusicSequence):
volume_processing_function=vol_processing_function, volume_processing_function=vol_processing_function,
default_tempo=default_tempo_value, default_tempo=default_tempo_value,
mismatch_error_ignorance=ignore_mismatch_error, mismatch_error_ignorance=ignore_mismatch_error,
note_referance_table_replacement=note_rtable_replacement,
) )
@classmethod @classmethod
@ -756,6 +765,7 @@ class MidiConvert(MusicSequence):
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,
note_table_replacement: Dict[str, str] = {},
): ):
""" """
直接输入文件地址将midi文件读入 直接输入文件地址将midi文件读入
@ -802,6 +812,7 @@ class MidiConvert(MusicSequence):
enable_old_exe_format=old_exe_format, enable_old_exe_format=old_exe_format,
minimum_volume=min_volume, minimum_volume=min_volume,
vol_processing_function=vol_processing_func, vol_processing_function=vol_processing_func,
note_rtable_replacement=note_table_replacement,
) )
except (ValueError, TypeError) as E: except (ValueError, TypeError) as E:
raise MidiDestroyedError(f"文件{midi_file_path}可能损坏:{E}") raise MidiDestroyedError(f"文件{midi_file_path}可能损坏:{E}")

View File

@ -18,9 +18,9 @@ Terms & Conditions: License.md in the root directory
from dataclasses import dataclass from dataclasses import dataclass
from .types import Optional, Any, List, Mapping, Tuple, Union from typing import Optional, Any, List, Tuple, Union
from .constants import MC_PERCUSSION_INSTRUMENT_LIST from .constants import MC_PITCHED_INSTRUMENT_LIST
@dataclass(init=False) @dataclass(init=False)
@ -91,7 +91,7 @@ class MineNote:
"""高精度开始时间偏量 0.4 毫秒""" """高精度开始时间偏量 0.4 毫秒"""
self.percussive = ( self.percussive = (
(mc_sound_name in MC_PERCUSSION_INSTRUMENT_LIST) (mc_sound_name not in MC_PITCHED_INSTRUMENT_LIST)
if (is_percussion is None) if (is_percussion is None)
else is_percussion else is_percussion
) )
@ -463,7 +463,7 @@ class SingleNoteBox:
self.annotation_text = annotation self.annotation_text = annotation
"""音符注释""" """音符注释"""
if percussion is None: if percussion is None:
self.is_percussion = percussion in MC_PERCUSSION_INSTRUMENT_LIST self.is_percussion = percussion not in MC_PITCHED_INSTRUMENT_LIST
else: else:
self.is_percussion = percussion self.is_percussion = percussion
@ -653,14 +653,3 @@ DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
""" """
默认的进度条样式 默认的进度条样式
""" """
MineNoteChannelType = Mapping[
int,
List[MineNote,],
]
"""
我的世界频道信息类型
Dict[int,Dict[int,List[MineNote,],],]
"""

View File

@ -17,19 +17,17 @@ Terms & Conditions: License.md in the root directory
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import ( from typing import (
Any,
Dict, Dict,
List, List,
Literal, Literal,
Optional,
Tuple, Tuple,
Union, Union,
Iterable,
Sequence,
Mapping, Mapping,
Callable, Callable,
) )
from .subclass import MineNote
MidiNoteNameTableType = Mapping[int, Tuple[str, ...]] MidiNoteNameTableType = Mapping[int, Tuple[str, ...]]
""" """
@ -64,3 +62,14 @@ ChannelType = Dict[
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],] Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
""" """
MineNoteChannelType = Mapping[
int,
List[MineNote,],
]
"""
我的世界频道信息类型
Dict[int,Dict[int,List[MineNote,],],]
"""

View File

@ -18,6 +18,22 @@ Terms & Conditions: License.md in the root directory
import math import math
import random import random
# from io import BytesIO
from typing import (
Any,
Dict,
Tuple,
Optional,
Callable,
Literal,
Union,
List,
Generator,
BinaryIO,
)
from xxhash import xxh3_64, xxh3_128
from .constants import ( from .constants import (
MC_INSTRUMENT_BLOCKS_TABLE, MC_INSTRUMENT_BLOCKS_TABLE,
MM_INSTRUMENT_DEVIATION_TABLE, MM_INSTRUMENT_DEVIATION_TABLE,
@ -26,16 +42,9 @@ from .constants import (
) )
from .subclass import MineNote, mctick2timestr from .subclass import MineNote, mctick2timestr
from .types import ( from .types import MidiInstrumentTableType, MineNoteChannelType
Any,
Dict, from .exceptions import MusicSequenceDecodeError
Tuple,
Optional,
Callable,
Literal,
Union,
MidiInstrumentTableType,
)
def empty_midi_channels( def empty_midi_channels(
@ -251,6 +260,7 @@ def midi_msgs_to_minenote(
play_speed: float, play_speed: float,
midi_reference_table: MidiInstrumentTableType, midi_reference_table: MidiInstrumentTableType,
volume_processing_method_: Callable[[float], float], volume_processing_method_: Callable[[float], float],
note_table_replacement: Dict[str, str] = {},
) -> MineNote: ) -> MineNote:
""" """
将Midi信息转为我的世界音符对象 将Midi信息转为我的世界音符对象
@ -275,7 +285,7 @@ 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_name=mc_sound_ID, mc_sound_name=note_table_replacement.get(mc_sound_ID, mc_sound_ID),
midi_pitch=note_, midi_pitch=note_,
midi_velocity=velocity_, midi_velocity=velocity_,
start_time=(tk := int(start_time_ / float(play_speed) / 50000)), start_time=(tk := int(start_time_ / float(play_speed) / 50000)),
@ -378,3 +388,291 @@ def soundID_to_blockID(
return random.choice(MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,))) return random.choice(MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,)))
else: else:
return MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,))[0] return MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,))[0]
def load_decode_msq_metainfo(
buffer_in: BinaryIO,
) -> Tuple[str, float, float, bool, int]:
"""
以流的方式解码MSQ音乐序列元信息
Parameters
----------
buffer_in: BytesIO
MSQ格式的字节流
Returns
-------
Tuple[str, float, float, bool, int]
音乐名称最小音量音调偏移是否启用高精度最后的流指针位置
"""
buffer_in.seek(4, 0)
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,
)
def load_decode_msq_flush_release(
buffer_in: BinaryIO,
starter_index: int,
high_quantity_note: bool,
) -> Generator[Tuple[int, MineNote], Any, None]:
"""以流的方式解码MSQ音乐序列的音符序列并流式返回
Parameters
----------
buffer_in : BytesIO
输入的MSQ格式二进制字节流
starter_index : int
字节流中音符序列的起始索引
high_quantity_note : bool
是否启用高精度音符解析
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)
buffer_in.seek(starter_index)
_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 = empty_midi_channels(
default_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,
),
__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 MusicSequenceDecodeError("难以定位的解码错误", _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
)

View File

@ -1,6 +1,12 @@
<h1 align="center"> [Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
· Musicreater [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
</h1> [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
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge
<h1 align="center">· Musicreater </h1>
<p align="center"> <p align="center">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png"> <img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png">
@ -92,9 +98,9 @@
- <table><tr><td>感谢 **油炸**&lt;QQ2836146704&gt; 激励我们不断开发新的内容</td><td><img height="50" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg"></td></tr></table> - <table><tr><td>感谢 **油炸**&lt;QQ2836146704&gt; 激励我们不断开发新的内容</td><td><img height="50" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg"></td></tr></table>
- 感谢 ****\<QQ237667809\> 反馈在新版本的指令格式下计分板播放器的附加包无法播放的问题 - 感谢 ****\<QQ237667809\> 反馈在新版本的指令格式下计分板播放器的附加包无法播放的问题
- 感谢 **梦幻duang**\<QQ13753593\> 为我们提供 Java 1.12.2 版本命令格式参考 - 感谢 **梦幻duang**\<QQ13753593\> 为我们提供 Java 1.12.2 版本命令格式参考
- 感谢 [_Open Note Block Studio_](https://github.com/OpenNBS/NoteBlockStudio) 项目的开发为我们提供持续的追赶动力
> 感谢广大群友为此库提供的测试和建议等 > 感谢广大群友为此库提供的测试和建议等
>
> 若您对我们有所贡献但您的名字没有出现在此列表中请联系我们 > 若您对我们有所贡献但您的名字没有出现在此列表中请联系我们
## 联系 📞 ## 联系 📞
@ -122,10 +128,3 @@ NOT AN OFFICIAL MINECRAFT PRODUCT.
NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
NOT APPROVED BY OR ASSOCIATED WITH NETEASE. NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%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
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge

View File

@ -1,3 +1,10 @@
[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
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge
<h1 align="center"> <h1 align="center">
· Musicreater · Musicreater
</h1> </h1>
@ -91,9 +98,10 @@ This list is not in any order.
- Thank _小埋_\<QQ2039310975\> for reporting the empty add-on packs title and description problem. - Thank _小埋_\<QQ2039310975\> for reporting the empty add-on packs title and description problem.
- <table><tr><td>Thank <i>油炸</i> &lt;QQ2836146704&gt; for inspiring us to constantly develop something new.</td><td><img width="260" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg" alt="The groupmate on the picture was saying that our convert-QQ-bot had once brought him great convinience but now it closed down by some reason so he was feeling regretful." title="&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;&#10;&quot;Until lose, I finally know cannot chase back what I needs&quot;"></td><td><small>&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;<br>&quot;Until lose, I finally know cannot chase back what I needs&quot;</small></td></tr></table> - <table><tr><td>Thank <i>油炸</i> &lt;QQ2836146704&gt; for inspiring us to constantly develop something new.</td><td><img width="260" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg" alt="The groupmate on the picture was saying that our convert-QQ-bot had once brought him great convinience but now it closed down by some reason so he was feeling regretful." title="&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;&#10;&quot;Until lose, I finally know cannot chase back what I needs&quot;"></td><td><small>&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;<br>&quot;Until lose, I finally know cannot chase back what I needs&quot;</small></td></tr></table>
- Thank _雨_\<QQ237667809\> for give us report that under the new `execute` command format that the scoreboard player's add-on packs cannot play correctly. - Thank _雨_\<QQ237667809\> for give us report that under the new `execute` command format that the scoreboard player's add-on packs cannot play correctly.
- Thank _梦幻duang_\<QQ13753593\> for providing us with his knowlodeg of the command format in Minecraft: Java Edition Version 1.12.2.
- Thank [_Open Note Block Studio_](https://github.com/OpenNBS/NoteBlockStudio)'s Project for giving us the power and energy of continual developing.
> Thanks for the support and help of a lot of groupmates > Thanks for the support and help of a lot of groupmates
>
> If you have given contributions but have not been in the list, please contact us! > If you have given contributions but have not been in the list, please contact us!
## Contact Us 📞 ## Contact Us 📞
@ -122,9 +130,3 @@ NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
- 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易璀璨网络科技有限公司 - 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易璀璨网络科技有限公司
[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
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge

View File

@ -13,9 +13,7 @@ def main():
if file.endswith(".egg-info"): if file.endswith(".egg-info"):
egg_info.append(file) egg_info.append(file)
console.print(file) console.print(file)
for file in track( for file in track(["build", "dist", "logs", *egg_info], description="正删档"):
["build", "dist", "logs", *egg_info], description="正删档"
):
if os.path.isdir(file) and os.access(file, os.W_OK): if os.path.isdir(file) and os.access(file, os.W_OK):
shutil.rmtree(file) shutil.rmtree(file)

View File

@ -91,6 +91,7 @@ ADD note_seq_2
ADD XXH64(seq_2_note_count, 3) ADD XXH64(seq_2_note_count, 3)
ADD XXH64(note_seq_2, seq_2_note_count) ADD XXH64(note_seq_2, seq_2_note_count)
ADD XXH128( ADD XXH128(
XOR(
XOR( XOR(
XXH64(meta_info, note_count), XXH64(meta_info, note_count),
XOR( XOR(
@ -98,6 +99,11 @@ ADD XXH128(
XXH64(note_seq_1, seq_1_note_count) XXH64(note_seq_1, seq_1_note_count)
), ),
), ),
XOR(
XXH64(seq_2_note_count, 3),
XXH64(note_seq_2, seq_2_note_count),
)
),
note_count note_count
) )
``` ```

View File

@ -1,14 +1,31 @@
import Musicreater import Musicreater
from Musicreater.utils import load_decode_msq_flush_release, load_decode_msq_metainfo
from rich.pretty import pprint
msc_seq = Musicreater.MusicSequence.from_mido( msc_seq = Musicreater.MusicSequence.from_mido(
Musicreater.mido.MidiFile("./resources/测试片段.mid",), Musicreater.mido.MidiFile(
"./resources/测试片段.mid",
),
"TEST-测试片段", "TEST-测试片段",
) )
with open("test.msq","wb") as f: pprint("音乐源取入成功:")
pprint(msc_seq)
with open("test.msq", "wb") as f:
f.write(msq_bytes := msc_seq.encode_dump()) f.write(msq_bytes := msc_seq.encode_dump())
with open("test.msq","rb") as f: with open("test.msq", "rb") as f:
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read()) msc_seq_r = Musicreater.MusicSequence.load_decode(f.read())
print(msc_seq_r) pprint("常规 MSQ 读取成功:")
pprint(msc_seq_r)
with open("test.msq", "rb") as f:
pprint("流式 MSQ 元数据:")
pprint(metas := load_decode_msq_metainfo(f))
pprint("流式 MSQ 音符序列:")
for i in load_decode_msq_flush_release(f, metas[-1], metas[-2]):
pprint(i)

View File

@ -6,10 +6,6 @@ Copyright © 2025 Eilles & bgArray
Terms & Conditions: License.md in the root directory Terms & Conditions: License.md in the root directory
""" """
import os import os
import shutil import shutil
@ -130,6 +126,15 @@ print(
input("midi路径"), input("midi路径"),
play_speed=float(input("播放速度:")), play_speed=float(input("播放速度:")),
old_exe_format=True, old_exe_format=True,
note_table_replacement={
"note.iron_xylophone": "note.xylophone",
"note.cow_bell": "note.xylophone",
"note.didgeridoo": "note.guitar",
"note.bit": "note.harp",
"note.banjo": "note.flute",
"note.pling": "note.harp",
},
# pitched_note_table=Musicreater.MM_NBS_PITCHED_INSTRUMENT_TABLE,
), ),
input("输出路径:"), input("输出路径:"),
Musicreater.experiment.ProgressBarStyle(), Musicreater.experiment.ProgressBarStyle(),