高精度时间支持,修复tempo设置错误的问题

This commit is contained in:
EillesWan 2024-06-11 00:54:50 +08:00
parent fe7c11636b
commit bf1b7b99d8
7 changed files with 456 additions and 359 deletions

View File

@ -17,20 +17,20 @@ Terms & Conditions: License.md in the root directory
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__version__ = "2.1.0.1" __version__ = "2.2.0"
__vername__ = "Websocket支持" __vername__ = "高精度时间支持"
__author__ = ( __author__ = (
("金羿", "Eilles Wan"), ("金羿", "Eilles Wan"),
("诸葛亮与八卦阵", "bgArray"), ("诸葛亮与八卦阵", "bgArray"),
("偷吃不是Touch", "Touch"),
("鱼旧梦", "ElapsingDreams"), ("鱼旧梦", "ElapsingDreams"),
("偷吃不是Touch", "Touch"),
) )
__all__ = [ __all__ = [
# 主要类 # 主要类
"MusicSequence", "MusicSequence",
"MidiConvert", "MidiConvert",
# 附加类 # 附加类
"SingleNote", # "SingleNote",
"MineNote", "MineNote",
"MineCommand", "MineCommand",
"SingleNoteBox", "SingleNoteBox",

View File

@ -51,7 +51,7 @@ class FutureMidiConvertM4(MidiConvert):
_apply_time_division: float = 10, _apply_time_division: float = 10,
) -> List[MineNote]: ) -> List[MineNote]:
"""传入音符数据,返回分割后的插值列表 """传入音符数据,返回分割后的插值列表
:param _note: SingleNote 音符 :param _note: MineNote 音符
:param _apply_time_division: int 间隔帧数 :param _apply_time_division: int 间隔帧数
:return list[tuple(int开始时间毫秒, int乐器, int音符, int力度内置, float音量播放),]""" :return list[tuple(int开始时间毫秒, int乐器, int音符, int力度内置, float音量播放),]"""
@ -81,7 +81,7 @@ class FutureMidiConvertM4(MidiConvert):
_note.start_tick + _i * (_note.duration / totalCount) _note.start_tick + _i * (_note.duration / totalCount)
), ),
last_time=int(_note.duration / totalCount), last_time=int(_note.duration / totalCount),
track_number=_note.track_no, # track_number=_note.track_no,
is_percussion=_note.percussive, is_percussion=_note.percussive,
extra_information=_note.extra_info, extra_information=_note.extra_info,
) )
@ -126,7 +126,7 @@ class FutureMidiConvertM4(MidiConvert):
) )
if not note.percussive: 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: else:
notes_list.append(note) notes_list.append(note)

View File

@ -38,7 +38,7 @@ from .utils import *
""" """
学习笔记 学习笔记
tempo: microseconds per quarter note 毫秒每四分音符换句话说就是一拍占多少 tempo: microseconds per quarter note 毫秒每四分音符换句话说就是一拍占多少
tick: midi帧 tick: midi帧
ticks_per_beat: 帧每拍即一拍多少帧 ticks_per_beat: 帧每拍即一拍多少帧
@ -46,11 +46,11 @@ ticks_per_beat: 帧每拍,即一拍多少帧
tick / ticks_per_beat => amount_of_beats 拍数(四分音符数) 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: seconds:
tick * tempo / 1000000.0 / ticks_per_beat tick * tempo / 1000000.0 / ticks_per_beat
microseconds: milliseconds:
tick * tempo / 1000.0 / ticks_per_beat tick * tempo / 1000.0 / ticks_per_beat
gameticks: gameticks:
@ -225,8 +225,13 @@ class MusicSequence:
"""从字节码导入音乐序列""" """从字节码导入音乐序列"""
group_1 = int.from_bytes(bytes_buffer_in[4:6], "big") 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( music_name_ = bytes_buffer_in[8 : (stt_index := 8 + (group_1 >> 10))].decode(
"utf-8" "GB18030"
) )
channels_: MineNoteChannelType = empty_midi_channels(staff=[]) channels_: MineNoteChannelType = empty_midi_channels(staff=[])
for channel_index in channels_.keys(): for channel_index in channels_.keys():
@ -236,9 +241,17 @@ class MusicSequence:
) )
): ):
try: 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( 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 stt_index = end_index
except: except:
@ -249,15 +262,21 @@ class MusicSequence:
name_of_music=music_name_, name_of_music=music_name_,
channels_of_notes=channels_, channels_of_notes=channels_,
minimum_volume_of_music=(group_1 & 0b1111111111) / 1000, minimum_volume_of_music=(group_1 & 0b1111111111) / 1000,
deviation_value=int.from_bytes(bytes_buffer_in[6:8], "big", signed=True) deviation_value=(
/ 1000, (-1 if group_2 & 0b100000000000000 else 1)
* (group_2 & 0b11111111111111)
/ 1000
),
) )
def encode_dump( def encode_dump(
self, self,
high_time_precision: bool = True,
) -> bytes: ) -> bytes:
"""将音乐序列转为二进制字节码""" """将音乐序列转为二进制字节码"""
# 第一版的码头: MSQ# 字串编码: UTF-8
# 第一版格式
# 音乐名称长度 6 位 支持到 63 # 音乐名称长度 6 位 支持到 63
# 最小音量 minimum_volume 10 位 最大支持 1023 即三位小数 # 最小音量 minimum_volume 10 位 最大支持 1023 即三位小数
# 共 16 位 合 2 字节 # 共 16 位 合 2 字节
@ -266,20 +285,59 @@ class MusicSequence:
# 共 16 位 合 2 字节 # 共 16 位 合 2 字节
# +++ # +++
# 音乐名称 music_name 长度最多63 支持到 21 个中文字符 或 63 个西文字符 # 音乐名称 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 = ( 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) + round(self.minimum_volume * 1000)
).to_bytes(2, "big") ).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 + r
) )
# 若启用“高精度”,则在每个音符前添加一个字节,用于存储音符时间控制精度偏移
# 此值每增加 1则音符向后播放时长增加 1/1250 秒
for channel_index, note_list in self.channels.items(): for channel_index, note_list in self.channels.items():
bytes_buffer += len(note_list).to_bytes(4, "big") bytes_buffer += len(note_list).to_bytes(4, "big")
for note_ in note_list: for note_ in note_list:
bytes_buffer += note_.encode() bytes_buffer += note_.encode(is_high_time_precision=high_time_precision)
return bytes_buffer return bytes_buffer
@ -374,8 +432,6 @@ class MusicSequence:
---------- ----------
midi: mido.MidiFile 对象 midi: mido.MidiFile 对象
需要处理的midi对象 需要处理的midi对象
ignore_mismatch_error bool
是否在导入时忽略音符不匹配错误
speed: float speed: float
音乐播放速度倍数 音乐播放速度倍数
default_tempo_value: int default_tempo_value: int
@ -398,115 +454,110 @@ class MusicSequence:
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
midi_channels: MineNoteChannelType = empty_midi_channels(staff=[]) midi_channels: MineNoteChannelType = empty_midi_channels(staff=[])
channel_program: Dict[int, int] = empty_midi_channels(staff=-1)
tempo = default_tempo_value tempo = default_tempo_value
note_count = 0 note_count = 0
note_count_per_instrument: Dict[str, int] = {} note_count_per_instrument: Dict[str, int] = {}
microseconds = 0
# 我们来用通道统计音乐信息 note_queue_A: Dict[
# 但是是用分轨的思路的 int,
for track_no, track in enumerate(midi.tracks): List[
microseconds = 0 Tuple[
if not track: int,
continue int,
]
],
] = empty_midi_channels(staff=[])
note_queue_B: Dict[
int,
List[
Tuple[
int,
int,
]
],
] = empty_midi_channels(staff=[])
note_queue_A: Dict[ # 直接使用mido.midifiles.tracks.merge_tracks转为单轨
int, # 采用的时遍历信息思路
List[ for msg in midi.merged_track:
Tuple[ if msg.time != 0:
int, # 微秒
int, microseconds += msg.time * tempo / midi.ticks_per_beat
]
],
] = empty_midi_channels(staff=[])
note_queue_B: Dict[
int,
List[
Tuple[
int,
int,
]
],
] = empty_midi_channels(staff=[])
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: elif msg.type == "note_on" and msg.velocity != 0:
if msg.time != 0: note_queue_A[msg.channel].append(
microseconds += msg.time * tempo / midi.ticks_per_beat / 1000 (msg.note, channel_program[msg.channel])
)
note_queue_B[msg.channel].append((msg.velocity, microseconds))
if msg.is_meta: elif (msg.type == "note_off") or (
if msg.type == "set_tempo": msg.type == "note_on" and msg.velocity == 0
tempo = msg.tempo ):
else: if (msg.note, channel_program[msg.channel]) in note_queue_A[
if msg.type == "program_change": msg.channel
channel_program[msg.channel] = msg.program ]:
_velocity, _ms = note_queue_B[msg.channel][
elif msg.type == "note_on" and msg.velocity != 0: note_queue_A[msg.channel].index(
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(
(msg.note, channel_program[msg.channel]) (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( midi_channels[msg.channel].append(
that_note := midi_msgs_to_minenote( that_note := midi_msgs_to_minenote(
inst_=( inst_=(
msg.note msg.note
if msg.channel == 9 if msg.channel == 9
else channel_program[msg.channel] else channel_program[msg.channel]
), ),
note_=( note_=(
channel_program[msg.channel] channel_program[msg.channel]
if msg.channel == 9 if msg.channel == 9
else msg.note else msg.note
), ),
velocity_=_velocity, velocity_=_velocity,
start_time_=_ms, start_time_=_ms, # 微秒
duration_=microseconds - _ms, duration_=microseconds - _ms, # 微秒
track_no_=track_no, percussive_=(msg.channel == 9),
percussive_=(msg.channel == 9), play_speed=speed,
play_speed=speed, midi_reference_table=(
midi_reference_table=( percussion_note_rtable
percussion_note_rtable if msg.channel == 9
if msg.channel == 9 else pitched_note_rtable
else pitched_note_rtable ),
), volume_processing_method_=vol_processing_function,
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: else:
if ignore_mismatch_error: raise NoteOnOffMismatchError(
print( "当前的MIDI很可能有损坏之嫌……",
"[WARRING] MIDI格式错误 音符不匹配 {} 无法在上文中找到与之匹配的音符开音消息".format( msg,
msg "无法在上文中找到与之匹配的音符开音消息。",
) )
)
else:
raise NoteOnOffMismatchError(
"当前的MIDI很可能有损坏之嫌……",
msg,
"无法在上文中找到与之匹配的音符开音消息。",
)
"""整合后的音乐通道格式 """整合后的音乐通道格式
每个通道包括若干消息元素其中逃不过这三种 每个通道包括若干消息元素其中逃不过这三种

View File

@ -272,3 +272,27 @@ def to_mcstructure_files_in_repeater_divided_by_instruments(
struct.dump(f) struct.dump(f)
return max_delay 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

View File

@ -42,8 +42,8 @@ class MineNote:
duration: int duration: int
"""音符持续时间 命令刻""" """音符持续时间 命令刻"""
track_no: int high_precision_time: int
"""音符所处的音轨""" """高精度开始时间偏量 1/1250 秒"""
percussive: bool percussive: bool
"""是否作为打击乐器启用""" """是否作为打击乐器启用"""
@ -61,7 +61,7 @@ class MineNote:
midi_velocity: int, midi_velocity: int,
start_time: int, start_time: int,
last_time: int, last_time: int,
track_number: int = 0, mass_precision_time: int = 0,
is_percussion: Optional[bool] = None, is_percussion: Optional[bool] = None,
displacement: Optional[Tuple[float, float, float]] = None, displacement: Optional[Tuple[float, float, float]] = None,
extra_information: Optional[Any] = None, extra_information: Optional[Any] = None,
@ -73,7 +73,7 @@ class MineNote:
:param start_time:`int` 开始之时(命令刻) :param start_time:`int` 开始之时(命令刻)
此处的时间是用从乐曲开始到当前的毫秒数 此处的时间是用从乐曲开始到当前的毫秒数
:param last_time:`int` 音符延续时间(命令刻) :param last_time:`int` 音符延续时间(命令刻)
:param track_number:`int` 音轨编号 :param mass_precision_time:`int` 高精度的开始时间偏移量(1/1250)
:param is_percussion:`bool` 是否作为打击乐器 :param is_percussion:`bool` 是否作为打击乐器
:param displacement:`tuple[int,int,int]` 声像位移 :param displacement:`tuple[int,int,int]` 声像位移
:param extra_information:`Any` 附加信息""" :param extra_information:`Any` 附加信息"""
@ -87,8 +87,8 @@ class MineNote:
"""开始之时 tick""" """开始之时 tick"""
self.duration: int = last_time self.duration: int = last_time
"""音符持续时间 tick""" """音符持续时间 tick"""
self.track_no: int = track_number self.high_precision_time: int = mass_precision_time
"""音符所处的音轨""" """高精度开始时间偏量 0.4 毫秒"""
self.percussive = ( self.percussive = (
(mc_sound_name in MC_PERCUSSION_INSTRUMENT_LIST) (mc_sound_name in MC_PERCUSSION_INSTRUMENT_LIST)
@ -105,7 +105,7 @@ class MineNote:
self.extra_info = extra_information self.extra_info = extra_information
@classmethod @classmethod
def decode(cls, code_buffer: bytes): def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
"""自字节码析出MineNote类""" """自字节码析出MineNote类"""
group_1 = int.from_bytes(code_buffer[:6], "big") group_1 = int.from_bytes(code_buffer[:6], "big")
percussive_ = bool(group_1 & 0b1) percussive_ = bool(group_1 & 0b1)
@ -117,17 +117,31 @@ class MineNote:
if code_buffer[6] & 0b1: if code_buffer[6] & 0b1:
position_displacement_ = ( position_displacement_ = (
int.from_bytes( 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", "big",
) )
/ 1000, / 1000,
int.from_bytes( 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", "big",
) )
/ 1000, / 1000,
int.from_bytes( 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", "big",
) )
/ 1000, / 1000,
@ -137,22 +151,28 @@ class MineNote:
try: try:
return cls( return cls(
mc_sound_name=code_buffer[8 : 8 + sound_name_length].decode( mc_sound_name=(
encoding="utf-8" 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_pitch=note_pitch_,
midi_velocity=code_buffer[6] >> 1, midi_velocity=code_buffer[6] >> 1,
start_time=start_tick_, start_time=start_tick_,
last_time=duration_, last_time=duration_,
track_number=code_buffer[7], mass_precision_time=code_buffer[7] if is_high_time_precision else 0,
is_percussion=percussive_, is_percussion=percussive_,
displacement=position_displacement_, displacement=position_displacement_,
) )
except: except:
print(code_buffer, "\n", code_buffer[8 : 8 + sound_name_length]) print(code_buffer, "\n", o)
raise 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 # is_displacement_included 长度 1 位 支持到 1
# 共 8 位 合 1 字节 # 共 8 位 合 1 字节
# +++ # +++
# (在第二版中已舍弃)
# track_no 长度 8 位 支持到 255 合 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 字节 # position_displacement 每个元素长 16 位 合 2 字节
# 共 48 位 合 6 字节 支持存储三位小数和两位整数,其值必须在 [0, 65.535] 之间 # 共 48 位 合 6 字节 支持存储三位小数和两位整数,其值必须在 [0, 65.535] 之间
@ -190,7 +215,7 @@ class MineNote:
( (
len( len(
r := self.sound_name.encode( r := self.sound_name.encode(
encoding="utf-8" encoding="GB18030"
) )
) )
<< 7 << 7
@ -210,7 +235,12 @@ class MineNote:
+ self.percussive + self.percussive
).to_bytes(6, "big") ).to_bytes(6, "big")
+ ((self.velocity << 1) + is_displacement_included).to_bytes(1, "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 + r
+ ( + (
( (
@ -227,7 +257,7 @@ class MineNote:
"""设置附加信息""" """设置附加信息"""
self.extra_info = sth 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( return "{}Note(Instrument = {}, {}Velocity = {}, StartTick = {}, Duration = {}{}{})".format(
"Percussive" if self.percussive else "", "Percussive" if self.percussive else "",
self.sound_name, self.sound_name,
@ -235,7 +265,6 @@ class MineNote:
self.velocity, self.velocity,
self.start_tick, self.start_tick,
self.duration, self.duration,
", Track = {}".format(self.track_no) if is_track else "",
( (
", PositionDisplacement = {}".format(self.position_displacement) ", PositionDisplacement = {}".format(self.position_displacement)
if is_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__() tuplized = self.__tuple__()
return ( return tuplized[:-2] + ((tuplized[-1],) if is_displacement else ())
tuplized[:-2]
+ ((tuplized[-2],) if is_track else ())
+ ((tuplized[-1],) if is_displacement else ())
)
def __list__(self) -> List: def __list__(self) -> List:
return ( return (
@ -259,7 +284,6 @@ class MineNote:
self.velocity, self.velocity,
self.start_tick, self.start_tick,
self.duration, self.duration,
self.track_no,
self.position_displacement, self.position_displacement,
] ]
if self.percussive if self.percussive
@ -270,7 +294,6 @@ class MineNote:
self.velocity, self.velocity,
self.start_tick, self.start_tick,
self.duration, self.duration,
self.track_no,
self.position_displacement, self.position_displacement,
] ]
) )
@ -278,8 +301,8 @@ class MineNote:
def __tuple__( def __tuple__(
self, self,
) -> Union[ ) -> 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, int, Tuple[float, float, float]],
Tuple[bool, str, int, int, int, Tuple[float, float, float]],
]: ]:
return ( return (
( (
@ -288,7 +311,6 @@ class MineNote:
self.velocity, self.velocity,
self.start_tick, self.start_tick,
self.duration, self.duration,
self.track_no,
self.position_displacement, self.position_displacement,
) )
if self.percussive if self.percussive
@ -299,7 +321,6 @@ class MineNote:
self.velocity, self.velocity,
self.start_tick, self.start_tick,
self.duration, self.duration,
self.track_no,
self.position_displacement, self.position_displacement,
) )
) )
@ -312,7 +333,6 @@ class MineNote:
"Velocity": self.velocity, "Velocity": self.velocity,
"StartTick": self.start_tick, "StartTick": self.start_tick,
"Duration": self.duration, "Duration": self.duration,
"Track": self.track_no,
"PositionDisplacement": self.position_displacement, "PositionDisplacement": self.position_displacement,
} }
if self.percussive if self.percussive
@ -323,7 +343,6 @@ class MineNote:
"Velocity": self.velocity, "Velocity": self.velocity,
"StartTick": self.start_tick, "StartTick": self.start_tick,
"Duration": self.duration, "Duration": self.duration,
"Track": self.track_no,
"PositionDisplacement": self.position_displacement, "PositionDisplacement": self.position_displacement,
} }
) )
@ -334,150 +353,150 @@ class MineNote:
return self.tuplize() == other.tuplize() return self.tuplize() == other.tuplize()
@dataclass(init=False) # @dataclass(init=False)
class SingleNote: # class SingleNote:
"""存储单个音符的类""" # """存储单个音符的类"""
instrument: int # instrument: int
"""乐器编号""" # """乐器编号"""
note: int # note: int
"""音符编号""" # """音符编号"""
velocity: int # velocity: int
"""力度/响度""" # """力度/响度"""
start_time: int # start_time: int
"""开始之时 ms""" # """开始之时 ms"""
duration: int # duration: int
"""音符持续时间 ms""" # """音符持续时间 ms"""
track_no: int # track_no: int
"""音符所处的音轨""" # """音符所处的音轨"""
percussive: bool # percussive: bool
"""是否为打击乐器""" # """是否为打击乐器"""
extra_info: Any # extra_info: Any
"""你觉得放什么好?""" # """你觉得放什么好?"""
def __init__( # def __init__(
self, # self,
instrument: int, # instrument: int,
pitch: int, # pitch: int,
velocity: int, # velocity: int,
startime: int, # startime: int,
lastime: int, # lastime: int,
is_percussion: bool, # is_percussion: bool,
track_number: int = 0, # track_number: int = 0,
extra_information: Any = None, # extra_information: Any = None,
): # ):
"""用于存储单个音符的类 # """用于存储单个音符的类
:param instrument 乐器编号 # :param instrument 乐器编号
:param pitch 音符编号 # :param pitch 音符编号
:param velocity 力度/响度 # :param velocity 力度/响度
:param startTime 开始之时(ms) # :param startTime 开始之时(ms)
此处的时间是用从乐曲开始到当前的毫秒数 # 注:此处的时间是用从乐曲开始到当前的毫秒数
:param lastTime 音符延续时间(ms)""" # :param lastTime 音符延续时间(ms)"""
self.instrument: int = instrument # self.instrument: int = instrument
"""乐器编号""" # """乐器编号"""
self.note: int = pitch # self.note: int = pitch
"""音符编号""" # """音符编号"""
self.velocity: int = velocity # self.velocity: int = velocity
"""力度/响度""" # """力度/响度"""
self.start_time: int = startime # self.start_time: int = startime
"""开始之时 ms""" # """开始之时 ms"""
self.duration: int = lastime # self.duration: int = lastime
"""音符持续时间 ms""" # """音符持续时间 ms"""
self.track_no: int = track_number # self.track_no: int = track_number
"""音符所处的音轨""" # """音符所处的音轨"""
self.percussive: bool = is_percussion # self.percussive: bool = is_percussion
"""是否为打击乐器""" # """是否为打击乐器"""
self.extra_info = extra_information # self.extra_info = extra_information
@property # @property
def inst(self) -> int: # def inst(self) -> int:
"""乐器编号""" # """乐器编号"""
return self.instrument # return self.instrument
@inst.setter # @inst.setter
def inst(self, inst_: int): # def inst(self, inst_: int):
self.instrument = inst_ # self.instrument = inst_
@property # @property
def pitch(self) -> int: # def pitch(self) -> int:
"""音符编号""" # """音符编号"""
return self.note # return self.note
# @property # # @property
# def get_mc_pitch(self,table: Dict[int, Tuple[str, int]]) -> float: # # 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",) # # 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) # # return -1 if self.percussive else 2 ** ((self.note - 60 - _X) / 12)
def set_info(self, sth: Any): # def set_info(self, sth: Any):
"""设置附加信息""" # """设置附加信息"""
self.extra_info = sth # self.extra_info = sth
def __str__(self, is_track: bool = False): # def __str__(self, is_track: bool = False):
return "{}Note(Instrument = {}, {}Velocity = {}, StartTime = {}, Duration = {}{})".format( # return "{}Note(Instrument = {}, {}Velocity = {}, StartTime = {}, Duration = {}{})".format(
"Percussive" if self.percussive else "", # "Percussive" if self.percussive else "",
self.inst, # self.inst,
"" if self.percussive else "Pitch = {}, ".format(self.pitch), # "" if self.percussive else "Pitch = {}, ".format(self.pitch),
self.start_time, # self.start_time,
self.duration, # self.duration,
", Track = {}".format(self.track_no) if is_track else "", # ", Track = {}".format(self.track_no) if is_track else "",
) # )
def __tuple__(self): # def __tuple__(self):
return ( # return (
( # (
self.percussive, # self.percussive,
self.inst, # self.inst,
self.velocity, # self.velocity,
self.start_time, # self.start_time,
self.duration, # self.duration,
self.track_no, # self.track_no,
) # )
if self.percussive # if self.percussive
else ( # else (
self.percussive, # self.percussive,
self.inst, # self.inst,
self.note, # self.note,
self.velocity, # self.velocity,
self.start_time, # self.start_time,
self.duration, # self.duration,
self.track_no, # self.track_no,
) # )
) # )
def __dict__(self): # def __dict__(self):
return ( # return (
{ # {
"Percussive": self.percussive, # "Percussive": self.percussive,
"Instrument": self.inst, # "Instrument": self.inst,
"Velocity": self.velocity, # "Velocity": self.velocity,
"StartTime": self.start_time, # "StartTime": self.start_time,
"Duration": self.duration, # "Duration": self.duration,
"Track": self.track_no, # "Track": self.track_no,
} # }
if self.percussive # if self.percussive
else { # else {
"Percussive": self.percussive, # "Percussive": self.percussive,
"Instrument": self.inst, # "Instrument": self.inst,
"Pitch": self.note, # "Pitch": self.note,
"Velocity": self.velocity, # "Velocity": self.velocity,
"StartTime": self.start_time, # "StartTime": self.start_time,
"Duration": self.duration, # "Duration": self.duration,
"Track": self.track_no, # "Track": self.track_no,
} # }
) # )
def __eq__(self, other) -> bool: # def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__): # if not isinstance(other, self.__class__):
return False # return False
return self.__str__() == other.__str__() # return self.__str__() == other.__str__()
@dataclass(init=False) @dataclass(init=False)
@ -736,7 +755,10 @@ class ProgressBarStyle:
.replace(r"%^s", str(total_delays)) .replace(r"%^s", str(total_delays))
.replace(r"%%t", mctick2timestr(played_delays)) .replace(r"%%t", mctick2timestr(played_delays))
.replace(r"%^t", mctick2timestr(total_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( .replace(
"_", "_",
self.played_style, self.played_style,
@ -762,15 +784,15 @@ DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
默认的进度条样式 默认的进度条样式
""" """
NoteChannelType = Mapping[ # NoteChannelType = Mapping[
int, # int,
List[SingleNote,], # List[SingleNote,],
] # ]
""" # """
频道信息类型 # 频道信息类型
Dict[int,Dict[int,List[SingleNote,],],] # Dict[int,Dict[int,List[SingleNote,],],]
""" # """
MineNoteChannelType = Mapping[ MineNoteChannelType = Mapping[

View File

@ -24,7 +24,7 @@ from .constants import (
MC_PITCHED_INSTRUMENT_LIST, MC_PITCHED_INSTRUMENT_LIST,
MM_INSTRUMENT_RANGE_TABLE, MM_INSTRUMENT_RANGE_TABLE,
) )
from .subclass import SingleNote, MineNote, mctick2timestr from .subclass import MineNote, mctick2timestr
from .types import ( from .types import (
Any, Any,
@ -38,7 +38,6 @@ from .types import (
) )
def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]: def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]:
""" """
空MIDI通道字典 空MIDI通道字典
@ -199,41 +198,41 @@ def minenote_to_command_paramaters(
) )
def single_note_to_command_parameters( # def single_note_to_command_parameters(
note_: SingleNote, # note_: SingleNote,
reference_table: MidiInstrumentTableType, # reference_table: MidiInstrumentTableType,
deviation: float = 0, # deviation: float = 0,
volume_processing_method: Callable[[float], float] = natural_curve, # volume_processing_method: Callable[[float], float] = natural_curve,
) -> Tuple[ # ) -> Tuple[
str, # str,
Tuple[float, float, float], # Tuple[float, float, float],
float, # float,
Union[float, Literal[None]], # Union[float, Literal[None]],
]: # ]:
""" # """
将音符转为播放的指令之参数 # 将音符转为播放的指令之参数
:param note_:int 音符对象 # :param note_:int 音符对象
:param reference_table:Dict[int, str] 转换对照表 # :param reference_table:Dict[int, str] 转换对照表
:param deviation:float 音调偏移量 # :param deviation:float 音调偏移量
:param volume_proccessing_method:Callable[[float], 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( # mc_sound_ID, _X = inst_to_sould_with_deviation(
note_.inst, # note_.inst,
reference_table, # reference_table,
"note.bd" if note_.percussive else "note.flute", # "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 ( # return (
mc_sound_ID, # mc_sound_ID,
(0, mc_distance_volume, 0), # (0, mc_distance_volume, 0),
note_.velocity / 127, # note_.velocity / 127,
None if note_.percussive else 2 ** ((note_.pitch - 60 - _X + deviation) / 12), # None if note_.percussive else 2 ** ((note_.pitch - 60 - _X + deviation) / 12),
) # )
def midi_msgs_to_minenote( def midi_msgs_to_minenote(
@ -243,7 +242,6 @@ def midi_msgs_to_minenote(
velocity_: int, velocity_: int,
start_time_: int, start_time_: int,
duration_: int, duration_: int,
track_no_: int,
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],
@ -254,9 +252,8 @@ def midi_msgs_to_minenote(
:param note_: int 音高编号音符编号 :param note_: int 音高编号音符编号
:param percussive_: bool 是否作为打击乐器启用 :param percussive_: bool 是否作为打击乐器启用
:param velocity_: int 力度(响度) :param velocity_: int 力度(响度)
:param start_time_: int 音符起始时间毫秒数 :param start_time_: int 音符起始时间微秒
:param duration_: int 音符持续时间毫秒数 :param duration_: int 音符持续时间微秒
:param track_no_: int 音符所处音轨
:param play_speed: float 曲目播放速度 :param play_speed: float 曲目播放速度
:param midi_reference_table: Dict[int, str] 转换对照表 :param midi_reference_table: Dict[int, str] 转换对照表
:param volume_proccessing_method_: Callable[[float], float] 音量处理函数 :param volume_proccessing_method_: Callable[[float], float] 音量处理函数
@ -275,48 +272,47 @@ def midi_msgs_to_minenote(
mc_sound_name=mc_sound_ID, mc_sound_name=mc_sound_ID,
midi_pitch=note_, midi_pitch=note_,
midi_velocity=velocity_, midi_velocity=velocity_,
start_time=round(start_time_ / float(play_speed) / 50), start_time=(tk := int(start_time_ / float(play_speed) / 50000)),
last_time=round(duration_ / float(play_speed) / 50), last_time=round(duration_ / float(play_speed) / 50000),
track_number=track_no_, mass_precision_time=round((start_time_ / float(play_speed) - tk * 50000) / 800),
is_percussion=percussive_, is_percussion=percussive_,
displacement=(0, mc_distance_volume, 0), displacement=(0, mc_distance_volume, 0),
) )
def single_note_to_minenote( # def single_note_to_minenote(
note_: SingleNote, # note_: SingleNote,
reference_table: MidiInstrumentTableType, # reference_table: MidiInstrumentTableType,
play_speed: float = 0, # play_speed: float = 0,
volume_processing_method: Callable[[float], float] = natural_curve, # volume_processing_method: Callable[[float], float] = natural_curve,
) -> MineNote: # ) -> MineNote:
""" # """
将音符转为我的世界音符对象 # 将音符转为我的世界音符对象
:param note_:SingleNote 音符对象 # :param note_:SingleNote 音符对象
:param reference_table:Dict[int, str] 转换对照表 # :param reference_table:Dict[int, str] 转换对照表
:param play_speed:float 播放速度 # :param play_speed:float 播放速度
:param volume_proccessing_method:Callable[[float], float] 音量处理函数 # :param volume_proccessing_method:Callable[[float], float] 音量处理函数
:return MineNote我的世界音符对象 # :return MineNote我的世界音符对象
""" # """
mc_sound_ID = midi_inst_to_mc_sound( # mc_sound_ID = midi_inst_to_mc_sound(
note_.inst, # note_.inst,
reference_table, # reference_table,
"note.bd" if note_.percussive else "note.flute", # "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( # return MineNote(
mc_sound_ID, # mc_sound_name=mc_sound_ID,
note_.pitch, # midi_pitch=note_.pitch,
note_.velocity, # midi_velocity=note_.velocity,
round(note_.start_time / float(play_speed) / 50), # start_time=round(note_.start_time / float(play_speed) / 50),
round(note_.duration / float(play_speed) / 50), # last_time=round(note_.duration / float(play_speed) / 50),
note_.track_no, # is_percussion=note_.percussive,
note_.percussive, # displacement=(0, mc_distance_volume, 0),
(0, mc_distance_volume, 0), # extra_information=note_.extra_info,
note_.extra_info, # )
)
def is_in_diapason(note_pitch: int, instrument: str) -> bool: def is_in_diapason(note_pitch: int, instrument: str) -> bool:
@ -337,7 +333,7 @@ def note_to_redstone_block(
Parameters Parameters
---------- ----------
note_: SingleNote note_: MineNote
音符类 音符类
random_select: bool random_select: bool
是否随机选取对应方块 是否随机选取对应方块

View File

@ -10,8 +10,12 @@ print(
Musicreater.plugin.websocket.to_websocket_server( Musicreater.plugin.websocket.to_websocket_server(
[ [
Musicreater.MidiConvert.from_midi_file( Musicreater.MidiConvert.from_midi_file(
os.path.join(dire,names), old_exe_format=False os.path.join(dire, names), old_exe_format=False
) for names in os.listdir(dire,) if names.endswith((".mid",".midi")) )
for names in os.listdir(
dire,
)
if names.endswith((".mid", ".midi"))
], ],
input("服务器地址:"), input("服务器地址:"),
int(input("服务器端口:")), int(input("服务器端口:")),