diff --git a/Musicreater/__init__.py b/Musicreater/__init__.py index 60a2273..06d760c 100644 --- a/Musicreater/__init__.py +++ b/Musicreater/__init__.py @@ -22,8 +22,8 @@ The Licensor of Musicreater("this project") is Eilles Wan, bgArray. # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__version__ = "2.2.3" -__vername__ = "Java版欲适配、初步MSQ流式适配" +__version__ = "2.2.4" +__vername__ = "MSQ 流式适配与校验增强,新增 NBS 音色表" __author__ = ( ("金羿", "Eilles"), ("诸葛亮与八卦阵", "bgArray"), @@ -48,6 +48,14 @@ __all__ = [ "MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE", "MM_TOUCH_PITCHED_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 * diff --git a/Musicreater/constants.py b/Musicreater/constants.py index e331335..ea971de 100644 --- a/Musicreater/constants.py +++ b/Musicreater/constants.py @@ -16,7 +16,8 @@ Terms & Conditions: License.md in the root directory # Email TriM-Organization@hotmail.com # 若需转载或借鉴 许可声明请查看仓库目录下的 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" """ @@ -478,7 +479,12 @@ MM_INSTRUMENT_DEVIATION_TABLE: Dict[str, int] = { "fire.ignite": 0, "note.cow_bell": 6, } -"""不同乐器的音调偏离对照表""" +""" +不同乐器的音调偏离对照表 +*注意* 该表中的单位是对于 Midi Pitch 音调(整数)的低音偏移。 +也就是说,该数值越高,则在 Midi Pitch 中的值域越低 +默认的偏移量为 6 ,因为在计算音高时候少减去了 6 个 Pitch 单位 +""" # Midi乐器对MC乐器对照表 @@ -853,6 +859,8 @@ MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = { """“偷吃”打击乐器对照表""" # Dislink “断联” 音色对照表 +# https://github.com/Dislink/midi2bdx/blob/main/index.html + MM_DISLINK_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = { 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方块对照表 # 金羿ELS 音符方块对照表 diff --git a/Musicreater/main.py b/Musicreater/main.py index a13ec96..2fdffda 100644 --- a/Musicreater/main.py +++ b/Musicreater/main.py @@ -34,7 +34,6 @@ import os import math import mido -from xxhash import xxh3_64, xxh3_128 from .constants import * from .exceptions import * @@ -169,6 +168,7 @@ class MusicSequence: minimum_vol: float = 0.1, volume_processing_function: FittingFunctionType = natural_curve, deviation: float = 0, + note_referance_table_replacement: Dict[str, str] = {}, ): """ 自mido对象导入一个音符序列类 @@ -210,6 +210,7 @@ class MusicSequence: default_tempo_value=default_tempo, vol_processing_function=volume_processing_function, ignore_mismatch_error=mismatch_error_ignorance, + note_rtable_replacement=note_referance_table_replacement, ) else: note_channels = {} @@ -241,6 +242,7 @@ class MusicSequence: music_name_ = bytes_buffer_in[8 : (stt_index := 8 + (group_1 >> 10))].decode( "GB18030" ) + channels_: MineNoteChannelType = empty_midi_channels(default_staff=[]) total_note_count = 0 if verify: @@ -477,8 +479,11 @@ class MusicSequence: """重命名此音乐""" 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.total_note_count += 1 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, percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE, vol_processing_function: FittingFunctionType = natural_curve, + note_rtable_replacement: Dict[str, str] = {}, ) -> Tuple[MineNoteChannelType, int, Dict[str, int]]: """ 将midi解析并转换为频道音符字典 @@ -616,6 +622,7 @@ class MusicSequence: else pitched_note_rtable ), volume_processing_method_=vol_processing_function, + note_table_replacement=note_rtable_replacement, ) ) note_count += 1 @@ -693,6 +700,7 @@ class MidiConvert(MusicSequence): enable_old_exe_format: bool = False, minimum_volume: float = 0.1, vol_processing_function: FittingFunctionType = natural_curve, + note_rtable_replacement: Dict[str, str] = {}, ): """ 简单的midi转换类,将midi对象转换为我的世界结构或者包 @@ -742,6 +750,7 @@ class MidiConvert(MusicSequence): volume_processing_function=vol_processing_function, default_tempo=default_tempo_value, mismatch_error_ignorance=ignore_mismatch_error, + note_referance_table_replacement=note_rtable_replacement, ) @classmethod @@ -756,6 +765,7 @@ class MidiConvert(MusicSequence): old_exe_format: bool = False, min_volume: float = 0.1, vol_processing_func: FittingFunctionType = natural_curve, + note_table_replacement: Dict[str, str] = {}, ): """ 直接输入文件地址,将midi文件读入 @@ -802,6 +812,7 @@ class MidiConvert(MusicSequence): enable_old_exe_format=old_exe_format, minimum_volume=min_volume, vol_processing_function=vol_processing_func, + note_rtable_replacement=note_table_replacement, ) except (ValueError, TypeError) as E: raise MidiDestroyedError(f"文件{midi_file_path}可能损坏:{E}") diff --git a/Musicreater/subclass.py b/Musicreater/subclass.py index 66636ca..bb126e4 100644 --- a/Musicreater/subclass.py +++ b/Musicreater/subclass.py @@ -18,9 +18,9 @@ Terms & Conditions: License.md in the root directory 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) @@ -91,7 +91,7 @@ class MineNote: """高精度开始时间偏量 0.4 毫秒""" self.percussive = ( - (mc_sound_name in MC_PERCUSSION_INSTRUMENT_LIST) + (mc_sound_name not in MC_PITCHED_INSTRUMENT_LIST) if (is_percussion is None) else is_percussion ) @@ -463,7 +463,7 @@ class SingleNoteBox: self.annotation_text = annotation """音符注释""" if percussion is None: - self.is_percussion = percussion in MC_PERCUSSION_INSTRUMENT_LIST + self.is_percussion = percussion not in MC_PITCHED_INSTRUMENT_LIST else: self.is_percussion = percussion @@ -653,14 +653,3 @@ DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle( """ 默认的进度条样式 """ - - -MineNoteChannelType = Mapping[ - int, - List[MineNote,], -] -""" -我的世界频道信息类型 - -Dict[int,Dict[int,List[MineNote,],],] -""" diff --git a/Musicreater/types.py b/Musicreater/types.py index 6f18b88..a585cf2 100644 --- a/Musicreater/types.py +++ b/Musicreater/types.py @@ -17,19 +17,17 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md from typing import ( - Any, Dict, List, Literal, - Optional, Tuple, Union, - Iterable, - Sequence, Mapping, Callable, ) +from .subclass import MineNote + 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],]],],] """ + + +MineNoteChannelType = Mapping[ + int, + List[MineNote,], +] +""" +我的世界频道信息类型 + +Dict[int,Dict[int,List[MineNote,],],] +""" diff --git a/Musicreater/utils.py b/Musicreater/utils.py index d2eb470..466d7a8 100644 --- a/Musicreater/utils.py +++ b/Musicreater/utils.py @@ -18,6 +18,22 @@ Terms & Conditions: License.md in the root directory import math 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 ( MC_INSTRUMENT_BLOCKS_TABLE, MM_INSTRUMENT_DEVIATION_TABLE, @@ -26,16 +42,9 @@ from .constants import ( ) from .subclass import MineNote, mctick2timestr -from .types import ( - Any, - Dict, - Tuple, - Optional, - Callable, - Literal, - Union, - MidiInstrumentTableType, -) +from .types import MidiInstrumentTableType, MineNoteChannelType + +from .exceptions import MusicSequenceDecodeError def empty_midi_channels( @@ -251,6 +260,7 @@ def midi_msgs_to_minenote( play_speed: float, midi_reference_table: MidiInstrumentTableType, volume_processing_method_: Callable[[float], float], + note_table_replacement: Dict[str, str] = {}, ) -> MineNote: """ 将Midi信息转为我的世界音符对象 @@ -275,7 +285,7 @@ def midi_msgs_to_minenote( mc_distance_volume = volume_processing_method_(velocity_) 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_velocity=velocity_, 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,))) else: 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 + ) diff --git a/README.md b/README.md index 8d9eb69..e7bbe40 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ -
@@ -92,10 +98,10 @@
-
感谢 **油炸**<QQ2836146704> 激励我们不断开发新的内容。 | ![]() |
Thank 油炸 <QQ2836146704> for inspiring us to constantly develop something new. | ![]() | "It was once, a convert-QQ-bot is just in front my eyes" "Until lose, I finally know cannot chase back what I needs" |