高精度时间支持,修复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
__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",

View File

@ -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)

View File

@ -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,
"无法在上文中找到与之匹配的音符开音消息。",
)
"""整合后的音乐通道格式
每个通道包括若干消息元素其中逃不过这三种

View File

@ -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

View File

@ -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[

View File

@ -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
是否随机选取对应方块

View File

@ -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("服务器端口:")),