From bf1b7b99d8c397310139c5452f0130eb29c5c964 Mon Sep 17 00:00:00 2001 From: EillesWan Date: Tue, 11 Jun 2024 00:54:50 +0800 Subject: [PATCH] =?UTF-8?q?=E9=AB=98=E7=B2=BE=E5=BA=A6=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E4=BF=AE=E5=A4=8Dtempo=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Musicreater/__init__.py | 8 +- Musicreater/experiment.py | 6 +- Musicreater/main.py | 271 ++++++++++-------- Musicreater/plugin/mcstructfile/main.py | 24 ++ Musicreater/subclass.py | 358 +++++++++++++----------- Musicreater/utils.py | 140 +++++---- example_websocket.py | 8 +- 7 files changed, 456 insertions(+), 359 deletions(-) diff --git a/Musicreater/__init__.py b/Musicreater/__init__.py index 8c89f06..95be9fe 100644 --- a/Musicreater/__init__.py +++ b/Musicreater/__init__.py @@ -17,20 +17,20 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__version__ = "2.1.0.1" -__vername__ = "Websocket支持" +__version__ = "2.2.0" +__vername__ = "高精度时间支持" __author__ = ( ("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), - ("偷吃不是Touch", "Touch"), ("鱼旧梦", "ElapsingDreams"), + ("偷吃不是Touch", "Touch"), ) __all__ = [ # 主要类 "MusicSequence", "MidiConvert", # 附加类 - "SingleNote", + # "SingleNote", "MineNote", "MineCommand", "SingleNoteBox", diff --git a/Musicreater/experiment.py b/Musicreater/experiment.py index 63fcea0..a6c1007 100644 --- a/Musicreater/experiment.py +++ b/Musicreater/experiment.py @@ -51,7 +51,7 @@ class FutureMidiConvertM4(MidiConvert): _apply_time_division: float = 10, ) -> List[MineNote]: """传入音符数据,返回分割后的插值列表 - :param _note: SingleNote 音符 + :param _note: MineNote 音符 :param _apply_time_division: int 间隔帧数 :return list[tuple(int开始时间(毫秒), int乐器, int音符, int力度(内置), float音量(播放)),]""" @@ -81,7 +81,7 @@ class FutureMidiConvertM4(MidiConvert): _note.start_tick + _i * (_note.duration / totalCount) ), last_time=int(_note.duration / totalCount), - track_number=_note.track_no, + # track_number=_note.track_no, is_percussion=_note.percussive, extra_information=_note.extra_info, ) @@ -126,7 +126,7 @@ class FutureMidiConvertM4(MidiConvert): ) if not note.percussive: - notes_list.extend(self._linear_note(note,1 * note.extra_info[3])) + notes_list.extend(self._linear_note(note, 1 * note.extra_info[3])) else: notes_list.append(note) diff --git a/Musicreater/main.py b/Musicreater/main.py index d085a24..690d9c8 100644 --- a/Musicreater/main.py +++ b/Musicreater/main.py @@ -38,7 +38,7 @@ from .utils import * """ 学习笔记: -tempo: microseconds per quarter note 毫秒每四分音符,换句话说就是一拍占多少毫秒 +tempo: microseconds per quarter note 毫秒每四分音符,换句话说就是一拍占多少微秒 tick: midi帧 ticks_per_beat: 帧每拍,即一拍多少帧 @@ -46,11 +46,11 @@ ticks_per_beat: 帧每拍,即一拍多少帧 tick / ticks_per_beat => amount_of_beats 拍数(四分音符数) -tempo * amount_of_beats => 毫秒数 +tempo * amount_of_beats => 微秒数 所以: -tempo * tick / ticks_per_beat => 毫秒数 +tempo * tick / ticks_per_beat => 微秒数 ########### @@ -60,7 +60,7 @@ seconds per tick: seconds: tick * tempo / 1000000.0 / ticks_per_beat -microseconds: +milliseconds: tick * tempo / 1000.0 / ticks_per_beat gameticks: @@ -225,8 +225,13 @@ class MusicSequence: """从字节码导入音乐序列""" group_1 = int.from_bytes(bytes_buffer_in[4:6], "big") + group_2 = int.from_bytes(bytes_buffer_in[6:8], "big", signed=False) + + high_quantity = bool(group_2 & 0b1000000000000000) + # print(group_2, high_quantity) + music_name_ = bytes_buffer_in[8 : (stt_index := 8 + (group_1 >> 10))].decode( - "utf-8" + "GB18030" ) channels_: MineNoteChannelType = empty_midi_channels(staff=[]) for channel_index in channels_.keys(): @@ -236,9 +241,17 @@ class MusicSequence: ) ): try: - end_index = stt_index + 14 + (bytes_buffer_in[stt_index] >> 2) + end_index = ( + stt_index + + 13 + + high_quantity + + (bytes_buffer_in[stt_index] >> 2) + ) channels_[channel_index].append( - MineNote.decode(bytes_buffer_in[stt_index:end_index]) + MineNote.decode( + code_buffer=bytes_buffer_in[stt_index:end_index], + is_high_time_precision=high_quantity, + ) ) stt_index = end_index except: @@ -249,15 +262,21 @@ class MusicSequence: 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, + deviation_value=( + (-1 if group_2 & 0b100000000000000 else 1) + * (group_2 & 0b11111111111111) + / 1000 + ), ) def encode_dump( self, + high_time_precision: bool = True, ) -> bytes: """将音乐序列转为二进制字节码""" + # 第一版的码头: MSQ# 字串编码: UTF-8 + # 第一版格式 # 音乐名称长度 6 位 支持到 63 # 最小音量 minimum_volume 10 位 最大支持 1023 即三位小数 # 共 16 位 合 2 字节 @@ -266,20 +285,59 @@ class MusicSequence: # 共 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() + + # 第二版的码头: MSQ@ 字串编码: GB18030 + + # 音乐名称长度 6 位 支持到 63 + # 最小音量 minimum_volume 10 位 最大支持 1023 即三位小数 + # 共 16 位 合 2 字节 + # +++ + # 是否启用“高精度”音符时间控制 1 位 + # 总音调偏移 music_deviation 15 位 最大支持 -16383 ~ 16383 即 三位小数 + # 共 16 位 合 2 字节 + # +++ + # 音乐名称 music_name 长度最多63 支持到 31 个中文字符 或 63 个西文字符 + bytes_buffer = ( - b"MSQ#" + b"MSQ@" + ( - (len(r := self.music_name.encode("utf-8")) << 10) + (len(r := self.music_name.encode("GB18030")) << 10) + round(self.minimum_volume * 1000) ).to_bytes(2, "big") - + round(self.music_deviation * 1000).to_bytes(2, "big", signed=True) + + ( + ( + ( + (high_time_precision << 1) + + (1 if (k := round(self.music_deviation * 1000)) < 0 else 0) + ) + << 14 + ) + + abs(k) + ).to_bytes(2, "big", signed=False) + r ) + # 若启用“高精度”,则在每个音符前添加一个字节,用于存储音符时间控制精度偏移 + # 此值每增加 1,则音符向后播放时长增加 1/1250 秒 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() + bytes_buffer += note_.encode(is_high_time_precision=high_time_precision) return bytes_buffer @@ -374,8 +432,6 @@ class MusicSequence: ---------- midi: mido.MidiFile 对象 需要处理的midi对象 - ignore_mismatch_error: bool - 是否在导入时忽略音符不匹配错误 speed: float 音乐播放速度倍数 default_tempo_value: int @@ -398,115 +454,110 @@ class MusicSequence: # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 midi_channels: MineNoteChannelType = empty_midi_channels(staff=[]) + channel_program: Dict[int, int] = empty_midi_channels(staff=-1) tempo = default_tempo_value note_count = 0 note_count_per_instrument: Dict[str, int] = {} + microseconds = 0 - # 我们来用通道统计音乐信息 - # 但是是用分轨的思路的 - for track_no, track in enumerate(midi.tracks): - microseconds = 0 - if not track: - continue + note_queue_A: Dict[ + int, + List[ + Tuple[ + int, + int, + ] + ], + ] = empty_midi_channels(staff=[]) + note_queue_B: Dict[ + int, + List[ + Tuple[ + int, + int, + ] + ], + ] = empty_midi_channels(staff=[]) - note_queue_A: Dict[ - int, - List[ - Tuple[ - int, - int, - ] - ], - ] = empty_midi_channels(staff=[]) - note_queue_B: Dict[ - int, - List[ - Tuple[ - int, - int, - ] - ], - ] = empty_midi_channels(staff=[]) + # 直接使用mido.midifiles.tracks.merge_tracks转为单轨 + # 采用的时遍历信息思路 + for msg in midi.merged_track: + if msg.time != 0: + # 微秒 + microseconds += msg.time * tempo / midi.ticks_per_beat - channel_program: Dict[int, int] = empty_midi_channels(staff=-1) + # 简化 + if msg.type == "set_tempo": + tempo = msg.tempo + else: + if msg.type == "program_change": + channel_program[msg.channel] = msg.program - for msg in track: - if msg.time != 0: - microseconds += msg.time * tempo / midi.ticks_per_beat / 1000 + elif msg.type == "note_on" and msg.velocity != 0: + note_queue_A[msg.channel].append( + (msg.note, channel_program[msg.channel]) + ) + note_queue_B[msg.channel].append((msg.velocity, microseconds)) - if msg.is_meta: - if msg.type == "set_tempo": - tempo = msg.tempo - else: - if msg.type == "program_change": - channel_program[msg.channel] = msg.program - - elif msg.type == "note_on" and msg.velocity != 0: - note_queue_A[msg.channel].append( - (msg.note, channel_program[msg.channel]) - ) - note_queue_B[msg.channel].append((msg.velocity, microseconds)) - - elif (msg.type == "note_off") or ( - msg.type == "note_on" and msg.velocity == 0 - ): - if (msg.note, channel_program[msg.channel]) in note_queue_A[ - msg.channel - ]: - _velocity, _ms = note_queue_B[msg.channel][ - note_queue_A[msg.channel].index( - (msg.note, channel_program[msg.channel]) - ) - ] - note_queue_A[msg.channel].remove( + elif (msg.type == "note_off") or ( + msg.type == "note_on" and msg.velocity == 0 + ): + if (msg.note, channel_program[msg.channel]) in note_queue_A[ + msg.channel + ]: + _velocity, _ms = note_queue_B[msg.channel][ + note_queue_A[msg.channel].index( (msg.note, channel_program[msg.channel]) ) - note_queue_B[msg.channel].remove((_velocity, _ms)) + ] + note_queue_A[msg.channel].remove( + (msg.note, channel_program[msg.channel]) + ) + note_queue_B[msg.channel].remove((_velocity, _ms)) - midi_channels[msg.channel].append( - that_note := midi_msgs_to_minenote( - inst_=( - msg.note - if msg.channel == 9 - else channel_program[msg.channel] - ), - note_=( - channel_program[msg.channel] - if msg.channel == 9 - else msg.note - ), - velocity_=_velocity, - start_time_=_ms, - duration_=microseconds - _ms, - track_no_=track_no, - percussive_=(msg.channel == 9), - play_speed=speed, - midi_reference_table=( - percussion_note_rtable - if msg.channel == 9 - else pitched_note_rtable - ), - volume_processing_method_=vol_processing_function, + midi_channels[msg.channel].append( + that_note := midi_msgs_to_minenote( + inst_=( + msg.note + if msg.channel == 9 + else channel_program[msg.channel] + ), + note_=( + channel_program[msg.channel] + if msg.channel == 9 + else msg.note + ), + velocity_=_velocity, + start_time_=_ms, # 微秒 + duration_=microseconds - _ms, # 微秒 + percussive_=(msg.channel == 9), + play_speed=speed, + midi_reference_table=( + percussion_note_rtable + if msg.channel == 9 + else pitched_note_rtable + ), + volume_processing_method_=vol_processing_function, + ) + ) + note_count += 1 + if that_note.sound_name in note_count_per_instrument.keys(): + note_count_per_instrument[that_note.sound_name] += 1 + else: + note_count_per_instrument[that_note.sound_name] = 1 + else: + if ignore_mismatch_error: + print( + "[WARRING] MIDI格式错误 音符不匹配 {} 无法在上文中找到与之匹配的音符开音消息".format( + msg ) ) - note_count += 1 - if that_note.sound_name in note_count_per_instrument.keys(): - note_count_per_instrument[that_note.sound_name] += 1 - else: - note_count_per_instrument[that_note.sound_name] = 1 else: - if ignore_mismatch_error: - print( - "[WARRING] MIDI格式错误 音符不匹配 {} 无法在上文中找到与之匹配的音符开音消息".format( - msg - ) - ) - else: - raise NoteOnOffMismatchError( - "当前的MIDI很可能有损坏之嫌……", - msg, - "无法在上文中找到与之匹配的音符开音消息。", - ) + raise NoteOnOffMismatchError( + "当前的MIDI很可能有损坏之嫌……", + msg, + "无法在上文中找到与之匹配的音符开音消息。", + ) """整合后的音乐通道格式 每个通道包括若干消息元素其中逃不过这三种: diff --git a/Musicreater/plugin/mcstructfile/main.py b/Musicreater/plugin/mcstructfile/main.py index d378562..e799eb9 100644 --- a/Musicreater/plugin/mcstructfile/main.py +++ b/Musicreater/plugin/mcstructfile/main.py @@ -272,3 +272,27 @@ def to_mcstructure_files_in_repeater_divided_by_instruments( struct.dump(f) return max_delay + + +def to_mcstructure_file_in_blocks( + midi_cvt: MidiConvert, + dist_path: str, + player: str = "@a", +): + """ + 将midi以方块形式转换为mcstructure结构文件 + + Parameters + ---------- + midi_cvt: MidiConvert 对象 + 用于转换的MidiConvert对象 + dist_path: str + 转换结果输出的目标路径 + player: str + 玩家选择器,默认为`@a` + + Returns + ------- + int音乐总延迟 + """ + pass diff --git a/Musicreater/subclass.py b/Musicreater/subclass.py index 531906b..7e8bd71 100644 --- a/Musicreater/subclass.py +++ b/Musicreater/subclass.py @@ -42,8 +42,8 @@ class MineNote: duration: int """音符持续时间 命令刻""" - track_no: int - """音符所处的音轨""" + high_precision_time: int + """高精度开始时间偏量 1/1250 秒""" percussive: bool """是否作为打击乐器启用""" @@ -61,7 +61,7 @@ class MineNote: midi_velocity: int, start_time: int, last_time: int, - track_number: int = 0, + mass_precision_time: int = 0, is_percussion: Optional[bool] = None, displacement: Optional[Tuple[float, float, float]] = None, extra_information: Optional[Any] = None, @@ -73,7 +73,7 @@ class MineNote: :param start_time:`int` 开始之时(命令刻) 注:此处的时间是用从乐曲开始到当前的毫秒数 :param last_time:`int` 音符延续时间(命令刻) - :param track_number:`int` 音轨编号 + :param mass_precision_time:`int` 高精度的开始时间偏移量(1/1250秒) :param is_percussion:`bool` 是否作为打击乐器 :param displacement:`tuple[int,int,int]` 声像位移 :param extra_information:`Any` 附加信息""" @@ -87,8 +87,8 @@ class MineNote: """开始之时 tick""" self.duration: int = last_time """音符持续时间 tick""" - self.track_no: int = track_number - """音符所处的音轨""" + self.high_precision_time: int = mass_precision_time + """高精度开始时间偏量 0.4 毫秒""" self.percussive = ( (mc_sound_name in MC_PERCUSSION_INSTRUMENT_LIST) @@ -105,7 +105,7 @@ class MineNote: self.extra_info = extra_information @classmethod - def decode(cls, code_buffer: bytes): + def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True): """自字节码析出MineNote类""" group_1 = int.from_bytes(code_buffer[:6], "big") percussive_ = bool(group_1 & 0b1) @@ -117,17 +117,31 @@ class MineNote: if code_buffer[6] & 0b1: position_displacement_ = ( int.from_bytes( - code_buffer[8 + sound_name_length : 10 + sound_name_length], + ( + code_buffer[8 + sound_name_length : 10 + sound_name_length] + if is_high_time_precision + else code_buffer[7 + sound_name_length : 9 + sound_name_length] + ), "big", ) / 1000, int.from_bytes( - code_buffer[10 + sound_name_length : 12 + sound_name_length], + ( + code_buffer[10 + sound_name_length : 12 + sound_name_length] + if is_high_time_precision + else code_buffer[9 + sound_name_length : 11 + sound_name_length] + ), "big", ) / 1000, int.from_bytes( - code_buffer[12 + sound_name_length : 14 + sound_name_length], + ( + code_buffer[12 + sound_name_length : 14 + sound_name_length] + if is_high_time_precision + else code_buffer[ + 11 + sound_name_length : 13 + sound_name_length + ] + ), "big", ) / 1000, @@ -137,22 +151,28 @@ class MineNote: try: return cls( - mc_sound_name=code_buffer[8 : 8 + sound_name_length].decode( - encoding="utf-8" - ), + mc_sound_name=( + o := ( + code_buffer[8 : 8 + sound_name_length] + if is_high_time_precision + else code_buffer[7 : 7 + sound_name_length] + ) + ).decode(encoding="GB18030"), midi_pitch=note_pitch_, midi_velocity=code_buffer[6] >> 1, start_time=start_tick_, last_time=duration_, - track_number=code_buffer[7], + mass_precision_time=code_buffer[7] if is_high_time_precision else 0, is_percussion=percussive_, displacement=position_displacement_, ) except: - print(code_buffer, "\n", code_buffer[8 : 8 + sound_name_length]) + print(code_buffer, "\n", o) raise - def encode(self, is_displacement_included: bool = True) -> bytes: + def encode( + self, is_displacement_included: bool = True, is_high_time_precision: bool = True + ) -> bytes: """ 将数据打包为字节码 @@ -172,9 +192,14 @@ class MineNote: # is_displacement_included 长度 1 位 支持到 1 # 共 8 位 合 1 字节 # +++ + # (在第二版中已舍弃) # track_no 长度 8 位 支持到 255 合 1 字节 + # (在第二版中新增) + # high_time_precision(可选)长度 8 位 支持到 255 合 1 字节 支持 1/1250 秒 # +++ - # sound_name 长度最多63 支持到 21 个中文字符 或 63 个西文字符 + # sound_name 长度最多 63 支持到 31 个中文字符 或 63 个西文字符 + # 第一版编码: UTF-8 + # 第二版编码: GB18030 # +++ # position_displacement 每个元素长 16 位 合 2 字节 # 共 48 位 合 6 字节 支持存储三位小数和两位整数,其值必须在 [0, 65.535] 之间 @@ -190,7 +215,7 @@ class MineNote: ( len( r := self.sound_name.encode( - encoding="utf-8" + encoding="GB18030" ) ) << 7 @@ -210,7 +235,12 @@ class MineNote: + self.percussive ).to_bytes(6, "big") + ((self.velocity << 1) + is_displacement_included).to_bytes(1, "big") - + self.track_no.to_bytes(1, "big") + # + self.track_no.to_bytes(1, "big") + + ( + self.high_precision_time.to_bytes(1, "big") + if is_high_time_precision + else b"" + ) + r + ( ( @@ -227,7 +257,7 @@ class MineNote: """设置附加信息""" self.extra_info = sth - def __str__(self, is_displacement: bool = False, is_track: bool = False): + def __str__(self, is_displacement: bool = False): return "{}Note(Instrument = {}, {}Velocity = {}, StartTick = {}, Duration = {}{}{})".format( "Percussive" if self.percussive else "", self.sound_name, @@ -235,7 +265,6 @@ class MineNote: self.velocity, self.start_tick, self.duration, - ", Track = {}".format(self.track_no) if is_track else "", ( ", PositionDisplacement = {}".format(self.position_displacement) if is_displacement @@ -243,13 +272,9 @@ class MineNote: ), ) - def tuplize(self, is_displacement: bool = False, is_track: bool = False): + def tuplize(self, is_displacement: bool = False): tuplized = self.__tuple__() - return ( - tuplized[:-2] - + ((tuplized[-2],) if is_track else ()) - + ((tuplized[-1],) if is_displacement else ()) - ) + return tuplized[:-2] + ((tuplized[-1],) if is_displacement else ()) def __list__(self) -> List: return ( @@ -259,7 +284,6 @@ class MineNote: self.velocity, self.start_tick, self.duration, - self.track_no, self.position_displacement, ] if self.percussive @@ -270,7 +294,6 @@ class MineNote: self.velocity, self.start_tick, self.duration, - self.track_no, self.position_displacement, ] ) @@ -278,8 +301,8 @@ class MineNote: def __tuple__( self, ) -> Union[ - Tuple[bool, str, int, int, int, int, int, Tuple[float, float, float]], Tuple[bool, str, int, int, int, int, Tuple[float, float, float]], + Tuple[bool, str, int, int, int, Tuple[float, float, float]], ]: return ( ( @@ -288,7 +311,6 @@ class MineNote: self.velocity, self.start_tick, self.duration, - self.track_no, self.position_displacement, ) if self.percussive @@ -299,7 +321,6 @@ class MineNote: self.velocity, self.start_tick, self.duration, - self.track_no, self.position_displacement, ) ) @@ -312,7 +333,6 @@ class MineNote: "Velocity": self.velocity, "StartTick": self.start_tick, "Duration": self.duration, - "Track": self.track_no, "PositionDisplacement": self.position_displacement, } if self.percussive @@ -323,7 +343,6 @@ class MineNote: "Velocity": self.velocity, "StartTick": self.start_tick, "Duration": self.duration, - "Track": self.track_no, "PositionDisplacement": self.position_displacement, } ) @@ -334,150 +353,150 @@ class MineNote: return self.tuplize() == other.tuplize() -@dataclass(init=False) -class SingleNote: - """存储单个音符的类""" +# @dataclass(init=False) +# class SingleNote: +# """存储单个音符的类""" - instrument: int - """乐器编号""" +# instrument: int +# """乐器编号""" - note: int - """音符编号""" +# note: int +# """音符编号""" - velocity: int - """力度/响度""" +# velocity: int +# """力度/响度""" - start_time: int - """开始之时 ms""" +# start_time: int +# """开始之时 ms""" - duration: int - """音符持续时间 ms""" +# duration: int +# """音符持续时间 ms""" - track_no: int - """音符所处的音轨""" +# track_no: int +# """音符所处的音轨""" - percussive: bool - """是否为打击乐器""" +# percussive: bool +# """是否为打击乐器""" - extra_info: Any - """你觉得放什么好?""" +# extra_info: Any +# """你觉得放什么好?""" - def __init__( - self, - instrument: int, - pitch: int, - velocity: int, - startime: int, - lastime: int, - is_percussion: bool, - track_number: int = 0, - extra_information: Any = None, - ): - """用于存储单个音符的类 - :param instrument 乐器编号 - :param pitch 音符编号 - :param velocity 力度/响度 - :param startTime 开始之时(ms) - 注:此处的时间是用从乐曲开始到当前的毫秒数 - :param lastTime 音符延续时间(ms)""" - self.instrument: int = instrument - """乐器编号""" - self.note: int = pitch - """音符编号""" - self.velocity: int = velocity - """力度/响度""" - self.start_time: int = startime - """开始之时 ms""" - self.duration: int = lastime - """音符持续时间 ms""" - self.track_no: int = track_number - """音符所处的音轨""" - self.percussive: bool = is_percussion - """是否为打击乐器""" +# def __init__( +# self, +# instrument: int, +# pitch: int, +# velocity: int, +# startime: int, +# lastime: int, +# is_percussion: bool, +# track_number: int = 0, +# extra_information: Any = None, +# ): +# """用于存储单个音符的类 +# :param instrument 乐器编号 +# :param pitch 音符编号 +# :param velocity 力度/响度 +# :param startTime 开始之时(ms) +# 注:此处的时间是用从乐曲开始到当前的毫秒数 +# :param lastTime 音符延续时间(ms)""" +# self.instrument: int = instrument +# """乐器编号""" +# self.note: int = pitch +# """音符编号""" +# self.velocity: int = velocity +# """力度/响度""" +# self.start_time: int = startime +# """开始之时 ms""" +# self.duration: int = lastime +# """音符持续时间 ms""" +# self.track_no: int = track_number +# """音符所处的音轨""" +# self.percussive: bool = is_percussion +# """是否为打击乐器""" - self.extra_info = extra_information +# self.extra_info = extra_information - @property - def inst(self) -> int: - """乐器编号""" - return self.instrument +# @property +# def inst(self) -> int: +# """乐器编号""" +# return self.instrument - @inst.setter - def inst(self, inst_: int): - self.instrument = inst_ +# @inst.setter +# def inst(self, inst_: int): +# self.instrument = inst_ - @property - def pitch(self) -> int: - """音符编号""" - return self.note +# @property +# def pitch(self) -> int: +# """音符编号""" +# return self.note - # @property - # def get_mc_pitch(self,table: Dict[int, Tuple[str, int]]) -> float: - # self.mc_sound_ID, _X = inst_to_sould_with_deviation(self.inst,table,"note.bd" if self.percussive else "note.flute",) - # return -1 if self.percussive else 2 ** ((self.note - 60 - _X) / 12) +# # @property +# # def get_mc_pitch(self,table: Dict[int, Tuple[str, int]]) -> float: +# # self.mc_sound_ID, _X = inst_to_sould_with_deviation(self.inst,table,"note.bd" if self.percussive else "note.flute",) +# # return -1 if self.percussive else 2 ** ((self.note - 60 - _X) / 12) - def set_info(self, sth: Any): - """设置附加信息""" - self.extra_info = sth +# def set_info(self, sth: Any): +# """设置附加信息""" +# self.extra_info = sth - def __str__(self, is_track: bool = False): - return "{}Note(Instrument = {}, {}Velocity = {}, StartTime = {}, Duration = {}{})".format( - "Percussive" if self.percussive else "", - self.inst, - "" if self.percussive else "Pitch = {}, ".format(self.pitch), - self.start_time, - self.duration, - ", Track = {}".format(self.track_no) if is_track else "", - ) +# def __str__(self, is_track: bool = False): +# return "{}Note(Instrument = {}, {}Velocity = {}, StartTime = {}, Duration = {}{})".format( +# "Percussive" if self.percussive else "", +# self.inst, +# "" if self.percussive else "Pitch = {}, ".format(self.pitch), +# self.start_time, +# self.duration, +# ", Track = {}".format(self.track_no) if is_track else "", +# ) - def __tuple__(self): - return ( - ( - self.percussive, - self.inst, - self.velocity, - self.start_time, - self.duration, - self.track_no, - ) - if self.percussive - else ( - self.percussive, - self.inst, - self.note, - self.velocity, - self.start_time, - self.duration, - self.track_no, - ) - ) +# def __tuple__(self): +# return ( +# ( +# self.percussive, +# self.inst, +# self.velocity, +# self.start_time, +# self.duration, +# self.track_no, +# ) +# if self.percussive +# else ( +# self.percussive, +# self.inst, +# self.note, +# self.velocity, +# self.start_time, +# self.duration, +# self.track_no, +# ) +# ) - def __dict__(self): - return ( - { - "Percussive": self.percussive, - "Instrument": self.inst, - "Velocity": self.velocity, - "StartTime": self.start_time, - "Duration": self.duration, - "Track": self.track_no, - } - if self.percussive - else { - "Percussive": self.percussive, - "Instrument": self.inst, - "Pitch": self.note, - "Velocity": self.velocity, - "StartTime": self.start_time, - "Duration": self.duration, - "Track": self.track_no, - } - ) +# def __dict__(self): +# return ( +# { +# "Percussive": self.percussive, +# "Instrument": self.inst, +# "Velocity": self.velocity, +# "StartTime": self.start_time, +# "Duration": self.duration, +# "Track": self.track_no, +# } +# if self.percussive +# else { +# "Percussive": self.percussive, +# "Instrument": self.inst, +# "Pitch": self.note, +# "Velocity": self.velocity, +# "StartTime": self.start_time, +# "Duration": self.duration, +# "Track": self.track_no, +# } +# ) - def __eq__(self, other) -> bool: - if not isinstance(other, self.__class__): - return False - return self.__str__() == other.__str__() +# def __eq__(self, other) -> bool: +# if not isinstance(other, self.__class__): +# return False +# return self.__str__() == other.__str__() @dataclass(init=False) @@ -736,7 +755,10 @@ class ProgressBarStyle: .replace(r"%^s", str(total_delays)) .replace(r"%%t", mctick2timestr(played_delays)) .replace(r"%^t", mctick2timestr(total_delays)) - .replace(r"%%%", "{:0>5.2f}%".format(int(10000 * played_delays / total_delays) / 100)) + .replace( + r"%%%", + "{:0>5.2f}%".format(int(10000 * played_delays / total_delays) / 100), + ) .replace( "_", self.played_style, @@ -762,15 +784,15 @@ DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle( 默认的进度条样式 """ -NoteChannelType = Mapping[ - int, - List[SingleNote,], -] -""" -频道信息类型 +# NoteChannelType = Mapping[ +# int, +# List[SingleNote,], +# ] +# """ +# 频道信息类型 -Dict[int,Dict[int,List[SingleNote,],],] -""" +# Dict[int,Dict[int,List[SingleNote,],],] +# """ MineNoteChannelType = Mapping[ diff --git a/Musicreater/utils.py b/Musicreater/utils.py index e60f9a2..952fe4a 100644 --- a/Musicreater/utils.py +++ b/Musicreater/utils.py @@ -24,7 +24,7 @@ from .constants import ( MC_PITCHED_INSTRUMENT_LIST, MM_INSTRUMENT_RANGE_TABLE, ) -from .subclass import SingleNote, MineNote, mctick2timestr +from .subclass import MineNote, mctick2timestr from .types import ( Any, @@ -38,7 +38,6 @@ from .types import ( ) - def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]: """ 空MIDI通道字典 @@ -199,41 +198,41 @@ def minenote_to_command_paramaters( ) -def single_note_to_command_parameters( - note_: SingleNote, - reference_table: MidiInstrumentTableType, - deviation: float = 0, - volume_processing_method: Callable[[float], float] = natural_curve, -) -> Tuple[ - str, - Tuple[float, float, float], - float, - Union[float, Literal[None]], -]: - """ - 将音符转为播放的指令之参数 - :param note_:int 音符对象 - :param reference_table:Dict[int, str] 转换对照表 - :param deviation:float 音调偏移量 - :param volume_proccessing_method:Callable[[float], float] 音量处理函数 +# def single_note_to_command_parameters( +# note_: SingleNote, +# reference_table: MidiInstrumentTableType, +# deviation: float = 0, +# volume_processing_method: Callable[[float], float] = natural_curve, +# ) -> Tuple[ +# str, +# Tuple[float, float, float], +# float, +# Union[float, Literal[None]], +# ]: +# """ +# 将音符转为播放的指令之参数 +# :param note_:int 音符对象 +# :param reference_table:Dict[int, str] 转换对照表 +# :param deviation:float 音调偏移量 +# :param volume_proccessing_method:Callable[[float], float] 音量处理函数 - :return str[我的世界音符ID], Tuple[float,float,float]播放视角坐标, float[指令音量参数], float[指令音调参数] - """ +# :return str[我的世界音符ID], Tuple[float,float,float]播放视角坐标, float[指令音量参数], float[指令音调参数] +# """ - mc_sound_ID, _X = inst_to_sould_with_deviation( - note_.inst, - reference_table, - "note.bd" if note_.percussive else "note.flute", - ) +# mc_sound_ID, _X = inst_to_sould_with_deviation( +# note_.inst, +# reference_table, +# "note.bd" if note_.percussive else "note.flute", +# ) - mc_distance_volume = volume_processing_method(note_.velocity) +# mc_distance_volume = volume_processing_method(note_.velocity) - return ( - mc_sound_ID, - (0, mc_distance_volume, 0), - note_.velocity / 127, - None if note_.percussive else 2 ** ((note_.pitch - 60 - _X + deviation) / 12), - ) +# return ( +# mc_sound_ID, +# (0, mc_distance_volume, 0), +# note_.velocity / 127, +# None if note_.percussive else 2 ** ((note_.pitch - 60 - _X + deviation) / 12), +# ) def midi_msgs_to_minenote( @@ -243,7 +242,6 @@ def midi_msgs_to_minenote( velocity_: int, start_time_: int, duration_: int, - track_no_: int, play_speed: float, midi_reference_table: MidiInstrumentTableType, volume_processing_method_: Callable[[float], float], @@ -254,9 +252,8 @@ def midi_msgs_to_minenote( :param note_: int 音高编号(音符编号) :param percussive_: bool 是否作为打击乐器启用 :param velocity_: int 力度(响度) - :param start_time_: int 音符起始时间(毫秒数) - :param duration_: int 音符持续时间(毫秒数) - :param track_no_: int 音符所处音轨 + :param start_time_: int 音符起始时间(微秒) + :param duration_: int 音符持续时间(微秒) :param play_speed: float 曲目播放速度 :param midi_reference_table: Dict[int, str] 转换对照表 :param volume_proccessing_method_: Callable[[float], float] 音量处理函数 @@ -275,48 +272,47 @@ def midi_msgs_to_minenote( mc_sound_name=mc_sound_ID, midi_pitch=note_, midi_velocity=velocity_, - start_time=round(start_time_ / float(play_speed) / 50), - last_time=round(duration_ / float(play_speed) / 50), - track_number=track_no_, + 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_, displacement=(0, mc_distance_volume, 0), ) -def single_note_to_minenote( - note_: SingleNote, - reference_table: MidiInstrumentTableType, - play_speed: float = 0, - volume_processing_method: Callable[[float], float] = natural_curve, -) -> MineNote: - """ - 将音符转为我的世界音符对象 - :param note_:SingleNote 音符对象 - :param reference_table:Dict[int, str] 转换对照表 - :param play_speed:float 播放速度 - :param volume_proccessing_method:Callable[[float], float] 音量处理函数 +# 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", - ) +# :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) +# mc_distance_volume = volume_processing_method(note_.velocity) - return MineNote( - mc_sound_ID, - note_.pitch, - note_.velocity, - round(note_.start_time / float(play_speed) / 50), - round(note_.duration / float(play_speed) / 50), - note_.track_no, - note_.percussive, - (0, mc_distance_volume, 0), - note_.extra_info, - ) +# 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: int, instrument: str) -> bool: @@ -337,7 +333,7 @@ def note_to_redstone_block( Parameters ---------- - note_: SingleNote + note_: MineNote 音符类 random_select: bool 是否随机选取对应方块 diff --git a/example_websocket.py b/example_websocket.py index 93cb651..011136b 100644 --- a/example_websocket.py +++ b/example_websocket.py @@ -10,8 +10,12 @@ print( Musicreater.plugin.websocket.to_websocket_server( [ Musicreater.MidiConvert.from_midi_file( - os.path.join(dire,names), old_exe_format=False - ) for names in os.listdir(dire,) if names.endswith((".mid",".midi")) + os.path.join(dire, names), old_exe_format=False + ) + for names in os.listdir( + dire, + ) + if names.endswith((".mid", ".midi")) ], input("服务器地址:"), int(input("服务器端口:")),