From ffe5837c9f0fcfb1209f482b8f98f5f397ecce38 Mon Sep 17 00:00:00 2001
From: EillesWan
Date: Sun, 7 May 2023 19:25:51 +0800
Subject: [PATCH] =?UTF-8?q?=E4=B8=8D=E8=A6=81=E6=9B=B4=E6=96=B0=EF=BC=81?=
=?UTF-8?q?=E4=B8=8D=E8=A6=81=E6=9B=B4=E6=96=B0=EF=BC=81=E5=91=BD=E8=BF=90?=
=?UTF-8?q?=E7=9A=84=E9=BD=BF=E8=BD=AE=E5=B0=9A=E6=9C=AA=E5=BC=80=E5=A7=8B?=
=?UTF-8?q?=E8=BD=AC=E5=8A=A8=EF=BC=8C=E5=89=8D=E6=96=B9=E7=9A=84=E9=81=93?=
=?UTF-8?q?=E8=B7=AF=E5=B0=86=E4=BC=9A=E9=93=BA=E6=BB=A1=E6=B2=99=E5=B0=98?=
=?UTF-8?q?=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../{instConstants.py => constants.py} | 66 +-
Musicreater/experiment.py | 358 +++++++
Musicreater/magicmain.py | 89 +-
Musicreater/main.py | 924 ++----------------
Musicreater/plugin/__init__.py | 19 +
Musicreater/plugin/archive.py | 26 +
Musicreater/plugin/bdx.py | 199 ++++
Musicreater/plugin/common.py | 14 +
Musicreater/plugin/mcstructure.py | 237 +++++
Musicreater/previous.py | 449 +++++++++
Musicreater/subclass.py | 91 ++
Musicreater/utils.py | 460 +--------
README.md | 109 +--
README_EN.md | 101 +-
requirements.txt | 2 +-
15 files changed, 1726 insertions(+), 1418 deletions(-)
rename Musicreater/{instConstants.py => constants.py} (87%)
create mode 100644 Musicreater/experiment.py
create mode 100644 Musicreater/plugin/__init__.py
create mode 100644 Musicreater/plugin/archive.py
create mode 100644 Musicreater/plugin/bdx.py
create mode 100644 Musicreater/plugin/common.py
create mode 100644 Musicreater/plugin/mcstructure.py
create mode 100644 Musicreater/previous.py
create mode 100644 Musicreater/subclass.py
diff --git a/Musicreater/instConstants.py b/Musicreater/constants.py
similarity index 87%
rename from Musicreater/instConstants.py
rename to Musicreater/constants.py
index 8ddbd40..7bb6b27 100644
--- a/Musicreater/instConstants.py
+++ b/Musicreater/constants.py
@@ -1,4 +1,32 @@
-pitched_instrument_list = {
+"""
+存放常量与数值性内容
+"""
+
+
+x = "x"
+"""
+x
+"""
+
+y = "y"
+"""
+y
+"""
+
+z = "z"
+"""
+z
+"""
+
+DEFAULT_PROGRESSBAR_STYLE = (
+ r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]",
+ ("§e=§r", "§7=§r"),
+)
+"""
+默认的进度条样式组
+"""
+
+PITCHED_INSTRUMENT_LIST = {
0: ("note.harp", 6),
1: ("note.harp", 6),
2: ("note.pling", 6),
@@ -129,7 +157,7 @@ pitched_instrument_list = {
127: ("note.snare", 7), # 打击乐器无音域
}
-percussion_instrument_list = {
+PERCUSSION_INSTRUMENT_LIST = {
34: ("note.bd", 7),
35: ("note.bd", 7),
36: ("note.hat", 7),
@@ -179,7 +207,7 @@ percussion_instrument_list = {
80: ("note.bell", 4),
}
-instrument_to_blocks_list = {
+INSTRUMENT_BLOCKS_LIST = {
"note.bass": ("planks",),
"note.snare": ("sand",),
"note.hat": ("glass",),
@@ -198,3 +226,35 @@ instrument_to_blocks_list = {
"note.bassattack": ("command_block",), # 无法找到此音效
"note.harp": ("glass",),
}
+
+
+# 即将启用
+height2note = {
+ 0.5: 0,
+ 0.53: 1,
+ 0.56: 2,
+ 0.6: 3,
+ 0.63: 4,
+ 0.67: 5,
+ 0.7: 6,
+ 0.75: 7,
+ 0.8: 8,
+ 0.84: 9,
+ 0.9: 10,
+ 0.94: 11,
+ 1.0: 12,
+ 1.05: 13,
+ 1.12: 14,
+ 1.2: 15,
+ 1.25: 16,
+ 1.33: 17,
+ 1.4: 18,
+ 1.5: 19,
+ 1.6: 20,
+ 1.7: 21,
+ 1.8: 22,
+ 1.9: 23,
+ 2.0: 24,
+}
+"""音高对照表\n
+MC音高:音符盒音调"""
diff --git a/Musicreater/experiment.py b/Musicreater/experiment.py
new file mode 100644
index 0000000..a41da8e
--- /dev/null
+++ b/Musicreater/experiment.py
@@ -0,0 +1,358 @@
+'''
+新版本功能以及即将启用的函数
+'''
+
+
+
+from .exceptions import *
+from .subclass import *
+from .utils import *
+from .main import midiConvert, mido
+
+
+# 简单的单音填充
+def _toCmdList_m4(
+ self: midiConvert,
+ scoreboard_name: str = "mscplay",
+ MaxVolume: float = 1.0,
+ speed: float = 1.0,
+) -> list:
+ """
+ 使用金羿的转换思路,将midi转换为我的世界命令列表,并使用完全填充算法优化音感
+ :param scoreboard_name: 我的世界的计分板名称
+ :param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
+ :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
+ :return: tuple(命令列表, 命令个数, 计分板最大值)
+ """
+ # TODO: 这里的时间转换不知道有没有问题
+
+ if speed == 0:
+ if self.debug_mode:
+ raise ZeroSpeedError("播放速度仅可为正实数")
+ speed = 1
+ MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
+
+ # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
+ channels = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
+
+ # 我们来用通道统计音乐信息
+ for i, track in enumerate(self.midi.tracks):
+ microseconds = 0
+
+ for msg in track:
+ if msg.time != 0:
+ try:
+ microseconds += msg.time * tempo / self.midi.ticks_per_beat
+ except NameError:
+ raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
+
+ if msg.is_meta:
+ if msg.type == "set_tempo":
+ tempo = msg.tempo
+ else:
+ if self.debug_mode:
+ try:
+ if msg.channel > 15:
+ raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
+ except AttributeError:
+ pass
+
+ if msg.type == "program_change":
+ channels[msg.channel].append(
+ ("PgmC", msg.program, microseconds)
+ )
+
+ elif msg.type == "note_on" and msg.velocity != 0:
+ channels[msg.channel].append(
+ ("NoteS", msg.note, msg.velocity, microseconds)
+ )
+
+ elif (msg.type == "note_on" and msg.velocity == 0) or (
+ msg.type == "note_off"
+ ):
+ channels[msg.channel].append(("NoteE", msg.note, microseconds))
+
+ """整合后的音乐通道格式
+ 每个通道包括若干消息元素其中逃不过这三种:
+
+ 1 切换乐器消息
+
+ ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
+
+ 2 音符开始消息
+
+ ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
+
+ 3 音符结束消息
+
+ ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
+
+ note_channels = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
+
+ # 此处 我们把通道视为音轨
+ for i in range(len(channels)):
+ # 如果当前通道为空 则跳过
+
+ noteMsgs = []
+ MsgIndex = []
+
+ for msg in channels[i]:
+ if msg[0] == "PgmC":
+ InstID = msg[1]
+
+ elif msg[0] == "NoteS":
+ noteMsgs.append(msg[1:])
+ MsgIndex.append(msg[1])
+
+ elif msg[0] == "NoteE":
+ if msg[1] in MsgIndex:
+ note_channels[i].append(
+ SingleNote(
+ InstID,
+ msg[1],
+ noteMsgs[MsgIndex.index(msg[1])][1],
+ noteMsgs[MsgIndex.index(msg[1])][2],
+ msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2],
+ )
+ )
+ noteMsgs.pop(MsgIndex.index(msg[1]))
+ MsgIndex.pop(MsgIndex.index(msg[1]))
+
+ tracks = []
+ cmdAmount = 0
+ maxScore = 0
+ CheckFirstChannel = False
+
+ # 临时用的插值计算函数
+ def _linearFun(_note: SingleNote) -> list:
+ """传入音符数据,返回以半秒为分割的插值列表
+ :param _note: SingleNote 音符
+ :return list[tuple(int开始时间(毫秒), int乐器, int音符, int力度(内置), float音量(播放)),]"""
+
+ result = []
+
+ totalCount = int(_note.lastTime / 500)
+
+ for _i in range(totalCount):
+ result.append(
+ (
+ _note.startTime + _i * 500,
+ _note.instrument,
+ _note.pitch,
+ _note.velocity,
+ MaxVolume * ((totalCount - _i) / totalCount),
+ )
+ )
+
+ return result
+
+ # 此处 我们把通道视为音轨
+ for track in note_channels:
+ # 如果当前通道为空 则跳过
+ if not track:
+ continue
+
+ if note_channels.index(track) == 0:
+ CheckFirstChannel = True
+ SpecialBits = False
+ elif note_channels.index(track) == 9:
+ SpecialBits = True
+ else:
+ CheckFirstChannel = False
+ SpecialBits = False
+
+ nowTrack = []
+
+ for note in track:
+ for every_note in _linearFun(note):
+ # 应该是计算的时候出了点小问题
+ # 我们应该用一个MC帧作为时间单位而不是半秒
+
+ if SpecialBits:
+ soundID, _X = self.perc_inst_to_soundID_withX(InstID)
+ else:
+ soundID, _X = self.inst_to_souldID_withX(InstID)
+
+ score_now = round(every_note[0] / speed / 50000)
+
+ maxScore = max(maxScore, score_now)
+
+ nowTrack.append(
+ "execute @a[scores={"
+ + str(scoreboard_name)
+ + "="
+ + str(score_now)
+ + "}"
+ + f"] ~ ~ ~ playsound {soundID} @s ~ ~{1 / every_note[4] - 1} ~ "
+ f"{note.velocity * (0.7 if CheckFirstChannel else 0.9)} {2 ** ((note.pitch - 60 - _X) / 12)}"
+ )
+
+ cmdAmount += 1
+ tracks.append(nowTrack)
+
+ return [tracks, cmdAmount, maxScore]
+
+
+
+def to_note_list(
+ self,
+ speed: float = 1.0,
+) -> list:
+ """
+ 使用金羿的转换思路,将midi转换为我的世界音符盒所用的音高列表,并输出每个音符之后的延迟
+
+ Parameters
+ ----------
+ speed: float
+ 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
+
+ Returns
+ -------
+ tuple( list[tuple(str指令, int距离上一个指令的延迟 ),...], int音乐时长游戏刻 )
+ """
+
+ if speed == 0:
+ if self.debug_mode:
+ raise ZeroSpeedError("播放速度仅可为正实数")
+ speed = 1
+
+ # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
+ channels = empty_midi_channels()
+
+ # 我们来用通道统计音乐信息
+ # 但是是用分轨的思路的
+ for track_no, track in enumerate(self.midi.tracks):
+ microseconds = 0
+
+ for msg in track:
+ if msg.time != 0:
+ try:
+ microseconds += (
+ msg.time * tempo / self.midi.ticks_per_beat / 1000
+ )
+ # print(microseconds)
+ except NameError:
+ if self.debug_mode:
+ raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
+ else:
+ microseconds += (
+ msg.time
+ * mido.midifiles.midifiles.DEFAULT_TEMPO
+ / self.midi.ticks_per_beat
+ ) / 1000
+
+ if msg.is_meta:
+ if msg.type == "set_tempo":
+ tempo = msg.tempo
+ if self.debug_mode:
+ self.prt(f"TEMPO更改:{tempo}(毫秒每拍)")
+ else:
+ try:
+ if msg.channel > 15 and self.debug_mode:
+ raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
+ if not track_no in channels[msg.channel].keys():
+ channels[msg.channel][track_no] = []
+ except AttributeError:
+ pass
+
+ if msg.type == "program_change":
+ channels[msg.channel][track_no].append(
+ ("PgmC", msg.program, microseconds)
+ )
+
+ elif msg.type == "note_on" and msg.velocity != 0:
+ channels[msg.channel][track_no].append(
+ ("NoteS", msg.note, msg.velocity, microseconds)
+ )
+
+ elif (msg.type == "note_on" and msg.velocity == 0) or (
+ msg.type == "note_off"
+ ):
+ channels[msg.channel][track_no].append(
+ ("NoteE", msg.note, microseconds)
+ )
+
+ """整合后的音乐通道格式
+ 每个通道包括若干消息元素其中逃不过这三种:
+
+ 1 切换乐器消息
+ ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
+
+ 2 音符开始消息
+ ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
+
+ 3 音符结束消息
+ ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
+
+ tracks = {}
+
+ # 此处 我们把通道视为音轨
+ for i in channels.keys():
+ # 如果当前通道为空 则跳过
+ if not channels[i]:
+ continue
+
+ # 第十通道是打击乐通道
+ SpecialBits = True if i == 9 else False
+
+ # nowChannel = []
+
+ for track_no, track in channels[i].items():
+ for msg in track:
+ if msg[0] == "PgmC":
+ InstID = msg[1]
+
+ elif msg[0] == "NoteS":
+ try:
+ soundID, _X = (
+ self.perc_inst_to_soundID_withX(InstID)
+ if SpecialBits
+ else self.inst_to_souldID_withX(InstID)
+ )
+ except UnboundLocalError as E:
+ if self.debug_mode:
+ raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
+ else:
+ soundID, _X = (
+ self.perc_inst_to_soundID_withX(-1)
+ if SpecialBits
+ else self.inst_to_souldID_withX(-1)
+ )
+ score_now = round(msg[-1] / float(speed) / 50)
+ # print(score_now)
+
+ try:
+ tracks[score_now].append(
+ self.execute_cmd_head.format(player)
+ + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
+ f"{2 ** ((msg[1] - 60 - _X) / 12)}"
+ )
+ except KeyError:
+ tracks[score_now] = [
+ self.execute_cmd_head.format(player)
+ + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
+ f"{2 ** ((msg[1] - 60 - _X) / 12)}"
+ ]
+
+ all_ticks = list(tracks.keys())
+ all_ticks.sort()
+ results = []
+
+ for i in range(len(all_ticks)):
+ for j in range(len(tracks[all_ticks[i]])):
+ results.append(
+ (
+ tracks[all_ticks[i]][j],
+ (
+ 0
+ if j != 0
+ else (
+ all_ticks[i] - all_ticks[i - 1]
+ if i != 0
+ else all_ticks[i]
+ )
+ ),
+ )
+ )
+
+ return [results, max(all_ticks)]
\ No newline at end of file
diff --git a/Musicreater/magicmain.py b/Musicreater/magicmain.py
index b066390..fa657d8 100644
--- a/Musicreater/magicmain.py
+++ b/Musicreater/magicmain.py
@@ -212,7 +212,7 @@ if __name__ == '__main__':
from typing import Union
-from .utils import x,y,z,bottem_side_length_of_smallest_square_bottom_box,form_note_block_in_NBT_struct,form_repeater_in_NBT_struct
+from .plugin import x,y,z,bottem_side_length_of_smallest_square_bottom_box,form_note_block_in_NBT_struct,form_repeater_in_NBT_struct
# 不要用 没写完
def delay_to_note_blocks(
@@ -243,63 +243,54 @@ def delay_to_note_blocks(
# 1拍 x 2.5 rt
- def placeNoteBlock():
- for i in notes:
- error = True
- try:
- struct.set_block(
- [startpos[0], startpos[1] + 1, startpos[2]],
- form_note_block_in_NBT_struct(height2note[i[0]], instrument),
- )
- struct.set_block(startpos, Block("universal_minecraft", instuments[i[0]][1]),)
- error = False
- except ValueError:
+ for i in notes:
+ error = True
+ try:
+ struct.set_block(
+ [startpos[0], startpos[1] + 1, startpos[2]],
+ form_note_block_in_NBT_struct(height2note[i[0]], instrument),
+ )
+ struct.set_block(startpos, Block("universal_minecraft", instuments[i[0]][1]),)
+ error = False
+ except ValueError:
+ log("无法放置音符:" + str(i) + "于" + str(startpos))
+ struct.set_block(Block("universal_minecraft", baseblock), startpos)
+ struct.set_block(
+ Block("universal_minecraft", baseblock),
+ [startpos[0], startpos[1] + 1, startpos[2]],
+ )
+ finally:
+ if error is True:
log("无法放置音符:" + str(i) + "于" + str(startpos))
struct.set_block(Block("universal_minecraft", baseblock), startpos)
struct.set_block(
Block("universal_minecraft", baseblock),
[startpos[0], startpos[1] + 1, startpos[2]],
)
- finally:
- if error is True:
- log("无法放置音符:" + str(i) + "于" + str(startpos))
- struct.set_block(Block("universal_minecraft", baseblock), startpos)
- struct.set_block(
- Block("universal_minecraft", baseblock),
- [startpos[0], startpos[1] + 1, startpos[2]],
- )
- delay = int(i[1] * speed + 0.5)
- if delay <= 4:
+ delay = int(i[1] * speed + 0.5)
+ if delay <= 4:
+ startpos[0] += 1
+ struct.set_block(
+ form_repeater_in_NBT_struct(delay, "west"),
+ [startpos[0], startpos[1] + 1, startpos[2]],
+ )
+ struct.set_block(Block("universal_minecraft", baseblock), startpos)
+ else:
+ for j in range(int(delay / 4)):
startpos[0] += 1
struct.set_block(
- form_repeater_in_NBT_struct(delay, "west"),
+ form_repeater_in_NBT_struct(4, "west"),
[startpos[0], startpos[1] + 1, startpos[2]],
)
struct.set_block(Block("universal_minecraft", baseblock), startpos)
- else:
- for j in range(int(delay / 4)):
- startpos[0] += 1
- struct.set_block(
- form_repeater_in_NBT_struct(4, "west"),
- [startpos[0], startpos[1] + 1, startpos[2]],
- )
- struct.set_block(Block("universal_minecraft", baseblock), startpos)
- if delay % 4 != 0:
- startpos[0] += 1
- struct.set_block(
- form_repeater_in_NBT_struct(delay % 4, "west"),
- [startpos[0], startpos[1] + 1, startpos[2]],
- )
- struct.set_block(Block("universal_minecraft", baseblock), startpos)
- startpos[0] += posadder[0]
- startpos[1] += posadder[1]
- startpos[2] += posadder[2]
-
- # e = True
- try:
- placeNoteBlock()
- # e = False
- except: # ValueError
- log("无法放置方块了,可能是因为区块未加载叭")
-
+ if delay % 4 != 0:
+ startpos[0] += 1
+ struct.set_block(
+ form_repeater_in_NBT_struct(delay % 4, "west"),
+ [startpos[0], startpos[1] + 1, startpos[2]],
+ )
+ struct.set_block(Block("universal_minecraft", baseblock), startpos)
+ startpos[0] += posadder[0]
+ startpos[1] += posadder[1]
+ startpos[2] += posadder[2]
diff --git a/Musicreater/main.py b/Musicreater/main.py
index 5f77c32..22d7d94 100644
--- a/Musicreater/main.py
+++ b/Musicreater/main.py
@@ -20,96 +20,24 @@ Copyright © 2023 all the developers of Musicreater
Terms & Conditions: ../License.md
"""
+import os
+import math
import json
import shutil
import uuid
-from typing import TypeVar, Union
+from typing import TypeVar, Union, Tuple
-import brotli
import mido
from .exceptions import *
-from .instConstants import *
+from .constants import *
from .utils import *
+from .subclass import *
-T = TypeVar("T") # Declare type variable
VM = TypeVar("VM", mido.MidiFile, None) # void mido
-
-DEFAULT_PROGRESSBAR_STYLE = (
- r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]",
- ("§e=§r", "§7=§r"),
-)
-
-
-class SingleNote:
- def __init__(
- self, instrument: int, pitch: int, velocity: int, startTime: int, lastTime: int
- ):
- """用于存储单个音符的类
- :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.startTime: int = startTime
- """开始之时 ms"""
- self.lastTime: int = lastTime
- """音符持续时间 ms"""
-
- @property
- def inst(self):
- """乐器编号"""
- return self.instrument
-
- @inst.setter
- def inst(self, inst_):
- self.instrument = inst_
-
- @property
- def pitch(self):
- """音符编号"""
- return self.note
-
- def __str__(self):
- return (
- f"Note(inst = {self.inst}, pitch = {self.note}, velocity = {self.velocity}, "
- f"startTime = {self.startTime}, lastTime = {self.lastTime}, )"
- )
-
- def __tuple__(self):
- return self.inst, self.note, self.velocity, self.startTime, self.lastTime
-
- def __dict__(self):
- return {
- "inst": self.inst,
- "pitch": self.note,
- "velocity": self.velocity,
- "startTime": self.startTime,
- "lastTime": self.lastTime,
- }
-
-
-class MethodList(list):
- """函数列表,列表中的所有元素均为函数"""
-
- def __init__(self, in_=()):
- """函数列表,列表中的所有元素均为函数"""
- super().__init__()
- self._T = [_x for _x in in_]
-
- def __getitem__(self, item) -> T:
- return self._T[item]
-
- def __len__(self) -> int:
- return self._T.__len__()
-
+'''
+空Midi类类型
+'''
"""
学习笔记:
@@ -146,8 +74,17 @@ tick * tempo / 1000000.0 / ticks_per_beat * 一秒多少游戏刻
class midiConvert:
- def __init__(self, enable_old_exe_format: bool = True, debug: bool = False):
- """简单的midi转换类,将midi文件转换为我的世界结构或者包"""
+ def __init__(self, enable_old_exe_format: bool = False, debug: bool = False):
+ """
+ 简单的midi转换类,将midi文件转换为我的世界结构或者包
+
+ Parameters
+ ----------
+ enable_old_exe_format: bool
+ 是否启用旧版(≤1.19)指令格式,默认为否
+ debug: bool
+ 是否启用调试模式,默认为否
+ """
self.debug_mode: bool = debug
"""是否开启调试模式"""
@@ -167,25 +104,6 @@ class midiConvert:
self.execute_cmd_head = ""
"""execute 指令的执行开头,用于被format"""
- self.methods = MethodList(
- [
- self._toCmdList_m1,
- self._toCmdList_m2,
- self._toCmdList_m3,
- self._toCmdList_m4,
- ]
- )
- """转换算法列表,你觉得我为什么要这样调用函数?"""
-
- self.methods_byDelay = MethodList(
- [
- self._toCmdList_withDelay_m1,
- self._toCmdList_withDelay_m2,
- self._toCmdList_withDelay_m3,
- ]
- )
- """转换算法列表,但是是对于延迟播放器的,你觉得我为什么要这样调用函数?"""
-
self.enable_old_exe_format = enable_old_exe_format
"""是否启用旧版指令格式"""
@@ -194,7 +112,7 @@ class midiConvert:
if enable_old_exe_format
else "execute as {} at @s positioned ~ ~ ~ run "
)
- """execute指令的应用,两个版本提前决定。"""
+ """execute指令头部"""
def convert(self, midi_file: str, output_path: str):
"""转换前需要先运行此函数来获取基本信息"""
@@ -215,7 +133,7 @@ class midiConvert:
"""文件名,不含路径且不含后缀"""
@staticmethod
- def __Inst2soundID_withX(
+ def inst_to_souldID_withX(
instrumentID: int,
):
"""
@@ -239,12 +157,12 @@ class midiConvert:
tuple(str我的世界乐器名, int转换算法中的X)
"""
try:
- return pitched_instrument_list[instrumentID]
+ return PITCHED_INSTRUMENT_LIST[instrumentID]
except KeyError:
return "note.flute", 5
@staticmethod
- def __bitInst2ID_withX(instrumentID: int):
+ def perc_inst_to_soundID_withX(instrumentID: int):
"""
对于Midi第10通道所对应的打击乐器,返回我的世界乐器名
@@ -258,7 +176,7 @@ class midiConvert:
tuple(str我的世界乐器名, int转换算法中的X)
"""
try:
- return percussion_instrument_list[instrumentID]
+ return PERCUSSION_INSTRUMENT_LIST[instrumentID]
except KeyError:
print("WARN", f"无法使用打击乐器列表库,或者使用了不存在的乐器,打击乐器使用Dislink算法代替。{instrumentID}")
if instrumentID == 55:
@@ -270,14 +188,7 @@ class midiConvert:
else:
return "note.bd", 7
- @staticmethod
- def score2time(score: int):
- """
- 将《我的世界》的计分(以游戏刻计)转为表示时间的字符串
- """
- return str(int(int(score / 20) / 60)) + ":" + str(int(int(score / 20) % 60))
-
- def __form_progress_bar(
+ def form_progress_bar(
self,
max_score: int,
scoreboard_name: str,
@@ -316,6 +227,7 @@ class midiConvert:
| `_` | 用以表示进度条占位|
"""
perEach = max_score / pgs_style.count("_")
+ '''每个进度条代表的分值'''
result = []
@@ -323,7 +235,7 @@ class midiConvert:
pgs_style = pgs_style.replace(r"%^s", str(max_score))
if r"%^t" in pgs_style:
- pgs_style = pgs_style.replace(r"%^t", self.score2time(max_score))
+ pgs_style = pgs_style.replace(r"%^t", self.mctick2timestr(max_score))
sbn_pc = scoreboard_name[:2]
if r"%%%" in pgs_style:
@@ -465,251 +377,37 @@ class midiConvert:
return result
- def _toCmdList_m1(
+ def to_command_list(
self,
scoreboard_name: str = "mscplay",
- MaxVolume: float = 1.0,
- speed: float = 1.0,
- ) -> list:
- """
- 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表
- :param scoreboard_name: 我的世界的计分板名称
- :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
- :return: tuple(命令列表, 命令个数, 计分板最大值)
- """
- # :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
- tracks = []
- if speed == 0:
- if self.debug_mode:
- raise ZeroSpeedError("播放速度仅可为正实数")
- speed = 1
- MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
-
- commands = 0
- maxscore = 0
-
- # 分轨的思路其实并不好,但这个算法就是这样
- # 所以我建议用第二个方法 _toCmdList_m2
- for i, track in enumerate(self.midi.tracks):
- ticks = 0
- instrumentID = 0
- singleTrack = []
-
- for msg in track:
- ticks += msg.time
- if msg.is_meta:
- if msg.type == "set_tempo":
- tempo = msg.tempo
- else:
- if msg.type == "program_change":
- instrumentID = msg.program
-
- if msg.type == "note_on" and msg.velocity != 0:
- try:
- nowscore = round(
- (ticks * tempo)
- / ((self.midi.ticks_per_beat * float(speed)) * 50000)
- )
- except NameError:
- raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
- maxscore = max(maxscore, nowscore)
- if msg.channel == 9:
- soundID, _X = self.__bitInst2ID_withX(instrumentID)
- else:
- soundID, _X = self.__Inst2soundID_withX(instrumentID)
-
- singleTrack.append(
- "execute @a[scores={"
- + str(scoreboard_name)
- + "="
- + str(nowscore)
- + "}"
- + f"] ~ ~ ~ playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
- f"{2 ** ((msg.note - 60 - _X) / 12)}"
- )
- commands += 1
- if len(singleTrack) != 0:
- tracks.append(singleTrack)
-
- return [tracks, commands, maxscore]
-
- # 原本这个算法的转换效果应该和上面的算法相似的
- def _toCmdList_m2(
- self,
- scoreboard_name: str = "mscplay",
- MaxVolume: float = 1.0,
- speed: float = 1.0,
- ) -> list:
- """
- 使用神羽和金羿的转换思路,将midi转换为我的世界命令列表
- :param scoreboard_name: 我的世界的计分板名称
- :param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
- :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
- :return: tuple(命令列表, 命令个数, 计分板最大值)
- """
-
- if speed == 0:
- if self.debug_mode:
- raise ZeroSpeedError("播放速度仅可为正实数")
- speed = 1
- MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
-
- # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
- channels = {
- 0: [],
- 1: [],
- 2: [],
- 3: [],
- 4: [],
- 5: [],
- 6: [],
- 7: [],
- 8: [],
- 9: [],
- 10: [],
- 11: [],
- 12: [],
- 13: [],
- 14: [],
- 15: [],
- 16: [],
- }
-
- microseconds = 0
-
- # 我们来用通道统计音乐信息
- for msg in self.midi:
- microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样
- if not msg.is_meta:
- if self.debug_mode:
- try:
- if msg.channel > 15:
- raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
- except AttributeError:
- pass
-
- if msg.type == "program_change":
- channels[msg.channel].append(("PgmC", msg.program, microseconds))
-
- elif msg.type == "note_on" and msg.velocity != 0:
- channels[msg.channel].append(
- ("NoteS", msg.note, msg.velocity, microseconds)
- )
-
- elif (msg.type == "note_on" and msg.velocity == 0) or (
- msg.type == "note_off"
- ):
- channels[msg.channel].append(("NoteE", msg.note, microseconds))
-
- """整合后的音乐通道格式
- 每个通道包括若干消息元素其中逃不过这三种:
-
- 1 切换乐器消息
- ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
-
- 2 音符开始消息
- ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
-
- 3 音符结束消息
- ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
-
- tracks = []
- cmdAmount = 0
- maxScore = 0
-
- # 此处 我们把通道视为音轨
- for i in channels.keys():
- # 如果当前通道为空 则跳过
- if not channels[i]:
- continue
-
- if i == 9:
- SpecialBits = True
- else:
- SpecialBits = False
-
- nowTrack = []
-
- for msg in channels[i]:
- if msg[0] == "PgmC":
- InstID = msg[1]
-
- elif msg[0] == "NoteS":
- try:
- soundID, _X = (
- self.__bitInst2ID_withX(InstID)
- if SpecialBits
- else self.__Inst2soundID_withX(InstID)
- )
- except UnboundLocalError as E:
- if self.debug_mode:
- raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
- else:
- soundID, _X = (
- self.__bitInst2ID_withX(-1)
- if SpecialBits
- else self.__Inst2soundID_withX(-1)
- )
- score_now = round(msg[-1] / float(speed) / 50)
- maxScore = max(maxScore, score_now)
-
- nowTrack.append(
- self.execute_cmd_head.format(
- "@a[scores=({}={})]".format(scoreboard_name, score_now)
- .replace("(", r"{")
- .replace(")", r"}")
- )
- + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
- f"{2 ** ((msg[1] - 60 - _X) / 12)}"
- )
-
- cmdAmount += 1
-
- if nowTrack:
- tracks.append(nowTrack)
-
- return [tracks, cmdAmount, maxScore]
-
- def _toCmdList_m3(
- self,
- scoreboard_name: str = "mscplay",
- MaxVolume: float = 1.0,
+ max_volume: float = 1.0,
speed: float = 1.0,
) -> list:
"""
使用金羿的转换思路,将midi转换为我的世界命令列表
- :param scoreboard_name: 我的世界的计分板名称
- :param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
- :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
- :return: tuple(命令列表, 命令个数, 计分板最大值)
+
+ Parameters
+ ----------
+ scoreboard_name: str
+ 我的世界的计分板名称
+ max_volume: float
+ 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放
+ speed: float
+ 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
+
+ Returns
+ -------
+ tuple( list[list[str指令,... ],... ], int指令数量, int最大计分 )
"""
if speed == 0:
if self.debug_mode:
raise ZeroSpeedError("播放速度仅可为正实数")
speed = 1
- MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
+ max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
- channels = {
- 0: {},
- 1: {},
- 2: {},
- 3: {},
- 4: {},
- 5: {},
- 6: {},
- 7: {},
- 8: {},
- 9: {},
- 10: {},
- 11: {},
- 12: {},
- 13: {},
- 14: {},
- 15: {},
- 16: {},
- }
+ channels = empty_midi_channels()
# 我们来用通道统计音乐信息
# 但是是用分轨的思路的
@@ -802,18 +500,18 @@ class midiConvert:
elif msg[0] == "NoteS":
try:
soundID, _X = (
- self.__bitInst2ID_withX(InstID)
+ self.perc_inst_to_soundID_withX(InstID)
if SpecialBits
- else self.__Inst2soundID_withX(InstID)
+ else self.inst_to_souldID_withX(InstID)
)
except UnboundLocalError as E:
if self.debug_mode:
raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
else:
soundID, _X = (
- self.__bitInst2ID_withX(-1)
+ self.perc_inst_to_soundID_withX(-1)
if SpecialBits
- else self.__Inst2soundID_withX(-1)
+ else self.inst_to_souldID_withX(-1)
)
score_now = round(msg[-1] / float(speed) / 50)
maxScore = max(maxScore, score_now)
@@ -824,7 +522,7 @@ class midiConvert:
.replace("(", r"{")
.replace(")", r"}")
)
- + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
+ + f"playsound {soundID} @s ^ ^ ^{1 / max_volume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
@@ -835,442 +533,23 @@ class midiConvert:
return [tracks, cmdAmount, maxScore]
- # 简单的单音填充
- def _toCmdList_m4(
+
+ def to_command_list_with_delay(
self,
- scoreboard_name: str = "mscplay",
- MaxVolume: float = 1.0,
+ max_volume: float = 1.0,
speed: float = 1.0,
- ) -> list:
- """
- 使用金羿的转换思路,将midi转换为我的世界命令列表,并使用完全填充算法优化音感
- :param scoreboard_name: 我的世界的计分板名称
- :param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
- :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
- :return: tuple(命令列表, 命令个数, 计分板最大值)
- """
- # TODO: 这里的时间转换不知道有没有问题
-
- if speed == 0:
- if self.debug_mode:
- raise ZeroSpeedError("播放速度仅可为正实数")
- speed = 1
- MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
-
- # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
- channels = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
-
- # 我们来用通道统计音乐信息
- for i, track in enumerate(self.midi.tracks):
- microseconds = 0
-
- for msg in track:
- if msg.time != 0:
- try:
- microseconds += msg.time * tempo / self.midi.ticks_per_beat
- except NameError:
- raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
-
- if msg.is_meta:
- if msg.type == "set_tempo":
- tempo = msg.tempo
- else:
- if self.debug_mode:
- try:
- if msg.channel > 15:
- raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
- except AttributeError:
- pass
-
- if msg.type == "program_change":
- channels[msg.channel].append(
- ("PgmC", msg.program, microseconds)
- )
-
- elif msg.type == "note_on" and msg.velocity != 0:
- channels[msg.channel].append(
- ("NoteS", msg.note, msg.velocity, microseconds)
- )
-
- elif (msg.type == "note_on" and msg.velocity == 0) or (
- msg.type == "note_off"
- ):
- channels[msg.channel].append(("NoteE", msg.note, microseconds))
-
- """整合后的音乐通道格式
- 每个通道包括若干消息元素其中逃不过这三种:
-
- 1 切换乐器消息
-
- ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
-
- 2 音符开始消息
-
- ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
-
- 3 音符结束消息
-
- ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
-
- note_channels = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
-
- # 此处 我们把通道视为音轨
- for i in range(len(channels)):
- # 如果当前通道为空 则跳过
-
- noteMsgs = []
- MsgIndex = []
-
- for msg in channels[i]:
- if msg[0] == "PgmC":
- InstID = msg[1]
-
- elif msg[0] == "NoteS":
- noteMsgs.append(msg[1:])
- MsgIndex.append(msg[1])
-
- elif msg[0] == "NoteE":
- if msg[1] in MsgIndex:
- note_channels[i].append(
- SingleNote(
- InstID,
- msg[1],
- noteMsgs[MsgIndex.index(msg[1])][1],
- noteMsgs[MsgIndex.index(msg[1])][2],
- msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2],
- )
- )
- noteMsgs.pop(MsgIndex.index(msg[1]))
- MsgIndex.pop(MsgIndex.index(msg[1]))
-
- tracks = []
- cmdAmount = 0
- maxScore = 0
- CheckFirstChannel = False
-
- # 临时用的插值计算函数
- def _linearFun(_note: SingleNote) -> list:
- """传入音符数据,返回以半秒为分割的插值列表
- :param _note: SingleNote 音符
- :return list[tuple(int开始时间(毫秒), int乐器, int音符, int力度(内置), float音量(播放)),]"""
-
- result = []
-
- totalCount = int(_note.lastTime / 500)
-
- for _i in range(totalCount):
- result.append(
- (
- _note.startTime + _i * 500,
- _note.instrument,
- _note.pitch,
- _note.velocity,
- MaxVolume * ((totalCount - _i) / totalCount),
- )
- )
-
- return result
-
- # 此处 我们把通道视为音轨
- for track in note_channels:
- # 如果当前通道为空 则跳过
- if not track:
- continue
-
- if note_channels.index(track) == 0:
- CheckFirstChannel = True
- SpecialBits = False
- elif note_channels.index(track) == 9:
- SpecialBits = True
- else:
- CheckFirstChannel = False
- SpecialBits = False
-
- nowTrack = []
-
- for note in track:
- for every_note in _linearFun(note):
- # 应该是计算的时候出了点小问题
- # 我们应该用一个MC帧作为时间单位而不是半秒
-
- if SpecialBits:
- soundID, _X = self.__bitInst2ID_withX(InstID)
- else:
- soundID, _X = self.__Inst2soundID_withX(InstID)
-
- score_now = round(every_note[0] / speed / 50000)
-
- maxScore = max(maxScore, score_now)
-
- nowTrack.append(
- "execute @a[scores={"
- + str(scoreboard_name)
- + "="
- + str(score_now)
- + "}"
- + f"] ~ ~ ~ playsound {soundID} @s ~ ~{1 / every_note[4] - 1} ~ "
- f"{note.velocity * (0.7 if CheckFirstChannel else 0.9)} {2 ** ((note.pitch - 60 - _X) / 12)}"
- )
-
- cmdAmount += 1
- tracks.append(nowTrack)
-
- return [tracks, cmdAmount, maxScore]
-
- def _toCmdList_withDelay_m1(
- self,
- MaxVolume: float = 1.0,
- speed: float = 1.0,
- player: str = "@a",
- ) -> list:
- """
- 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
- :param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
- :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
- :param player: 玩家选择器,默认为`@a`
- :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
- """
- tracks = {}
-
- if speed == 0:
- if self.debug_mode:
- raise ZeroSpeedError("播放速度仅可为正实数")
- speed = 1
-
- MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
-
- for i, track in enumerate(self.midi.tracks):
- instrumentID = 0
- ticks = 0
-
- for msg in track:
- ticks += msg.time
- if msg.is_meta:
- if msg.type == "set_tempo":
- tempo = msg.tempo
- else:
- if msg.type == "program_change":
- instrumentID = msg.program
- if msg.type == "note_on" and msg.velocity != 0:
- now_tick = round(
- (ticks * tempo)
- / ((self.midi.ticks_per_beat * float(speed)) * 50000)
- )
- soundID, _X = self.__Inst2soundID_withX(instrumentID)
- try:
- tracks[now_tick].append(
- self.execute_cmd_head.format(player)
- + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
- f"{2 ** ((msg.note - 60 - _X) / 12)}"
- )
- except KeyError:
- tracks[now_tick] = [
- self.execute_cmd_head.format(player)
- + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
- f"{2 ** ((msg.note - 60 - _X) / 12)}"
- ]
-
- results = []
-
- all_ticks = list(tracks.keys())
- all_ticks.sort()
-
- for i in range(len(all_ticks)):
- if i != 0:
- for j in range(len(tracks[all_ticks[i]])):
- if j != 0:
- results.append((tracks[all_ticks[i]][j], 0))
- else:
- results.append(
- (tracks[all_ticks[i]][j], all_ticks[i] - all_ticks[i - 1])
- )
- else:
- for j in range(len(tracks[all_ticks[i]])):
- results.append((tracks[all_ticks[i]][j], all_ticks[i]))
-
- return [results, max(all_ticks)]
-
- def _toCmdList_withDelay_m2(
- self,
- MaxVolume: float = 1.0,
- speed: float = 1.0,
- player: str = "@a",
- ) -> list:
- """
- 使用神羽和金羿的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
- :param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
- :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
- :param player: 玩家选择器,默认为`@a`
- :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
- """
- tracks = {}
- if speed == 0:
- if self.debug_mode:
- raise ZeroSpeedError("播放速度仅可为正实数")
- speed = 1
-
- MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
-
- # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
- channels = {
- 0: [],
- 1: [],
- 2: [],
- 3: [],
- 4: [],
- 5: [],
- 6: [],
- 7: [],
- 8: [],
- 9: [],
- 10: [],
- 11: [],
- 12: [],
- 13: [],
- 14: [],
- 15: [],
- 16: [],
- }
-
- microseconds = 0
-
- # 我们来用通道统计音乐信息
- for msg in self.midi:
- try:
- microseconds += (
- msg.time * 1000
- ) # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样
-
- # print(microseconds)
- except NameError:
- if self.debug_mode:
- raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
- else:
- microseconds += (
- msg.time * 1000
- ) # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样
-
- if msg.is_meta:
- if msg.type == "set_tempo":
- tempo = msg.tempo
- else:
- if self.debug_mode:
- try:
- if msg.channel > 15:
- raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
- except AttributeError:
- pass
-
- if msg.type == "program_change":
- channels[msg.channel].append(("PgmC", msg.program, microseconds))
-
- elif msg.type == "note_on" and msg.velocity != 0:
- channels[msg.channel].append(
- ("NoteS", msg.note, msg.velocity, microseconds)
- )
-
- elif (msg.type == "note_on" and msg.velocity == 0) or (
- msg.type == "note_off"
- ):
- channels[msg.channel].append(("NoteE", msg.note, microseconds))
-
- """整合后的音乐通道格式
- 每个通道包括若干消息元素其中逃不过这三种:
-
- 1 切换乐器消息
- ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
-
- 2 音符开始消息
- ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
-
- 3 音符结束消息
- ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
-
- results = []
-
- for i in channels.keys():
- # 如果当前通道为空 则跳过
- if not channels[i]:
- continue
-
- if i == 9:
- SpecialBits = True
- else:
- SpecialBits = False
-
- for msg in channels[i]:
- if msg[0] == "PgmC":
- InstID = msg[1]
-
- elif msg[0] == "NoteS":
- try:
- soundID, _X = (
- self.__bitInst2ID_withX(InstID)
- if SpecialBits
- else self.__Inst2soundID_withX(InstID)
- )
- except UnboundLocalError as E:
- if self.debug_mode:
- raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
- else:
- soundID, _X = (
- self.__bitInst2ID_withX(-1)
- if SpecialBits
- else self.__Inst2soundID_withX(-1)
- )
- score_now = round(msg[-1] / float(speed) / 50)
-
- try:
- tracks[score_now].append(
- self.execute_cmd_head.format(player)
- + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
- f"{2 ** ((msg[1] - 60 - _X) / 12)}"
- )
- except KeyError:
- tracks[score_now] = [
- self.execute_cmd_head.format(player)
- + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
- f"{2 ** ((msg[1] - 60 - _X) / 12)}"
- ]
-
- all_ticks = list(tracks.keys())
- all_ticks.sort()
-
- for i in range(len(all_ticks)):
- for j in range(len(tracks[all_ticks[i]])):
- results.append(
- (
- tracks[all_ticks[i]][j],
- (
- 0
- if j != 0
- else (
- all_ticks[i] - all_ticks[i - 1]
- if i != 0
- else all_ticks[i]
- )
- ),
- )
- )
-
- return [results, max(all_ticks)]
-
- def _toCmdList_withDelay_m3(
- self,
- MaxVolume: float = 1.0,
- speed: float = 1.0,
- player: str = "@a",
+ player_selector: str = "@a",
) -> list:
"""
使用金羿的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
Parameters
----------
- MaxVolume: float
+ max_volume: float
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
- player: str
+ player_selector: str
玩家选择器,默认为`@a`
Returns
@@ -1282,28 +561,10 @@ class midiConvert:
if self.debug_mode:
raise ZeroSpeedError("播放速度仅可为正实数")
speed = 1
- MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
+ max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
- channels = {
- 0: {},
- 1: {},
- 2: {},
- 3: {},
- 4: {},
- 5: {},
- 6: {},
- 7: {},
- 8: {},
- 9: {},
- 10: {},
- 11: {},
- 12: {},
- 13: {},
- 14: {},
- 15: {},
- 16: {},
- }
+ channels = empty_midi_channels()
# 我们来用通道统计音乐信息
# 但是是用分轨的思路的
@@ -1391,32 +652,32 @@ class midiConvert:
elif msg[0] == "NoteS":
try:
soundID, _X = (
- self.__bitInst2ID_withX(InstID)
+ self.perc_inst_to_soundID_withX(InstID)
if SpecialBits
- else self.__Inst2soundID_withX(InstID)
+ else self.inst_to_souldID_withX(InstID)
)
except UnboundLocalError as E:
if self.debug_mode:
raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
else:
soundID, _X = (
- self.__bitInst2ID_withX(-1)
+ self.perc_inst_to_soundID_withX(-1)
if SpecialBits
- else self.__Inst2soundID_withX(-1)
+ else self.inst_to_souldID_withX(-1)
)
score_now = round(msg[-1] / float(speed) / 50)
# print(score_now)
try:
tracks[score_now].append(
- self.execute_cmd_head.format(player)
- + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
+ self.execute_cmd_head.format(player_selector)
+ + f"playsound {soundID} @s ^ ^ ^{1 / max_volume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
except KeyError:
tracks[score_now] = [
- self.execute_cmd_head.format(player)
- + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
+ self.execute_cmd_head.format(player_selector)
+ + f"playsound {soundID} @s ^ ^ ^{1 / max_volume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
]
@@ -1443,32 +704,37 @@ class midiConvert:
return [results, max(all_ticks)]
+
def to_mcpack(
self,
- method: int = 1,
volume: float = 1.0,
speed: float = 1.0,
- progressbar: Union[bool, tuple] = None,
+ progressbar: Union[bool, Tuple[str, Tuple[str,]]] = None,
scoreboard_name: str = "mscplay",
- isAutoReset: bool = False,
+ auto_reset: bool = False,
) -> tuple:
"""
- 使用method指定的转换算法,将midi转换为我的世界mcpack格式的包
- :param method: 转换算法
- :param isAutoReset: 是否自动重置计分板
- :param progressbar: 进度条,(当此参数为True时使用默认进度条,为其他的值为真的参数时识别为进度条自定义参数,为其他值为假的时候不生成进度条)
- :param scoreboard_name: 我的世界的计分板名称
- :param volume: 音量,注意:这里的音量范围为(0,1],其原理为在距离玩家 (1 / volume -1) 的地方播放音频
- :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
- :return 成功与否,成功返回(True,True),失败返回(False,str失败原因)
+ 将midi转换为我的世界mcpack格式的包
+
+ Parameters
+ ----------
+ volume: float
+ 音量,注意:这里的音量范围为(0,1],其原理为在距离玩家 (1 / volume -1) 的地方播放音频
+ speed: float
+ 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
+ progressbar: bool|tuple[str, Tuple[str,]]
+ 进度条,当此参数为 `True` 时使用默认进度条,为其他的**值为真**的参数时识别为进度条自定义参数,为其他**值为假**的时候不生成进度条
+ scoreboard_name: str
+ 我的世界的计分板名称
+ auto_reset: bool
+ 是否自动重置计分板
+
+ Returns
+ -------
+ tuple(int指令长度, int最大计分)
"""
- # try:
- cmdlist, maxlen, maxscore = self.methods[method - 1](
- scoreboard_name, volume, speed
- )
- # except:
- # return (False, f"无法找到算法ID{method}对应的转换算法")
+ cmdlist, maxlen, maxscore = self.to_command_list(scoreboard_name, volume, speed)
# 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目,然后创建
if os.path.exists(f"{self.output_path}/temp/functions/"):
@@ -1539,7 +805,7 @@ class midiConvert:
+ "..}]"
+ f" {scoreboard_name}\n"
)
- if isAutoReset
+ if auto_reset
else "",
f"function mscplay/progressShow\n" if progressbar else "",
)
@@ -1555,7 +821,7 @@ class midiConvert:
encoding="utf-8",
) as f:
f.writelines(
- "\n".join(self.__form_progress_bar(maxscore, scoreboard_name))
+ "\n".join(self.form_progress_bar(maxscore, scoreboard_name))
)
else:
with open(
@@ -1565,7 +831,7 @@ class midiConvert:
) as f:
f.writelines(
"\n".join(
- self.__form_progress_bar(
+ self.form_progress_bar(
maxscore, scoreboard_name, progressbar
)
)
@@ -1582,7 +848,7 @@ class midiConvert:
shutil.rmtree(f"{self.output_path}/temp/")
- return True, maxlen, maxscore
+ return maxlen, maxscore
def to_mcpack_with_delay(
self,
@@ -1711,7 +977,7 @@ class midiConvert:
pgb_struct, pgbSize, pgbNowPos = commands_to_structure(
[
(i, 0)
- for i in self.__form_progress_bar(max_delay, scb_name, progressbar)
+ for i in self.form_progress_bar(max_delay, scb_name, progressbar)
],
max_height - 1,
)
@@ -1898,11 +1164,11 @@ class midiConvert:
[
(i, 0)
for i in (
- self.__form_progress_bar(maxScore, scoreboard_name)
+ self.form_progress_bar(maxScore, scoreboard_name)
# 此处是对于仅有 True 的参数和自定义参数的判断
# 改这一行没🐎
if progressbar is True
- else self.__form_progress_bar(
+ else self.form_progress_bar(
maxScore, scoreboard_name, progressbar
)
)
@@ -2003,7 +1269,7 @@ class midiConvert:
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
[
(i, 0)
- for i in self.__form_progress_bar(max_delay, scb_name, progressbar)
+ for i in self.form_progress_bar(max_delay, scb_name, progressbar)
],
max_height - 1,
)
diff --git a/Musicreater/plugin/__init__.py b/Musicreater/plugin/__init__.py
new file mode 100644
index 0000000..a07c08d
--- /dev/null
+++ b/Musicreater/plugin/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+"""
+存放非音·创本体的附加内容(插件?)
+
+版权所有 © 2023 音·创 开发者
+Copyright © 2023 all the developers of Musicreater
+
+开源相关声明请见 ../../License.md
+Terms & Conditions: ../../License.md
+"""
+
+# 睿穆组织 开发交流群 861684859
+# Email TriM-Organization@hotmail.com
+# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
+# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
+
+
+__all__ = []
+__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon"))
\ No newline at end of file
diff --git a/Musicreater/plugin/archive.py b/Musicreater/plugin/archive.py
new file mode 100644
index 0000000..253b0f2
--- /dev/null
+++ b/Musicreater/plugin/archive.py
@@ -0,0 +1,26 @@
+
+
+
+import os
+import zipfile
+
+
+def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
+ """使用compression指定的算法打包目录为zip文件\n
+ 默认算法为DEFLATED(8),可用算法如下:\n
+ STORED = 0\n
+ DEFLATED = 8\n
+ BZIP2 = 12\n
+ LZMA = 14\n
+ """
+
+ zipf = zipfile.ZipFile(outFilename, "w", compression)
+ pre_len = len(os.path.dirname(sourceDir))
+ for parent, dirnames, filenames in os.walk(sourceDir):
+ for filename in filenames:
+ if filename == exceptFile:
+ continue
+ pathfile = os.path.join(parent, filename)
+ arc_name = pathfile[pre_len:].strip(os.path.sep) # 相对路径
+ zipf.write(pathfile, arc_name)
+ zipf.close()
\ No newline at end of file
diff --git a/Musicreater/plugin/bdx.py b/Musicreater/plugin/bdx.py
new file mode 100644
index 0000000..a9d8ffd
--- /dev/null
+++ b/Musicreater/plugin/bdx.py
@@ -0,0 +1,199 @@
+'''
+存放有关BDX结构操作的内容
+'''
+
+
+from .common import bottem_side_length_of_smallest_square_bottom_box
+from ..constants import x,y,z
+
+
+bdx_key = {
+ "x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"],
+ "y": [b"\x11", b"\x10", b"\x1d", b"\x16", b"\x17"],
+ "z": [b"\x13", b"\x12", b"\x1e", b"\x18", b"\x19"],
+}
+"""key存储了方块移动指令的数据,其中可以用key[x|y|z][0|1]来表示xyz的减或增
+而key[][2+]是用来增加指定数目的"""
+
+
+def bdx_move(axis: str, value: int):
+ if value == 0:
+ return b""
+ if abs(value) == 1:
+ return bdx_key[axis][0 if value == -1 else 1]
+
+ pointer = sum(
+ [
+ 1 if i else 0
+ for i in (
+ value != -1,
+ value < -1 or value > 1,
+ value < -128 or value > 127,
+ value < -32768 or value > 32767,
+ )
+ ]
+ )
+
+ return bdx_key[axis][pointer] + value.to_bytes(
+ 2 ** (pointer - 2), "big", signed=True
+ )
+
+
+
+def form_command_block_in_BDX_bytes(
+ command: str,
+ particularValue: int,
+ impluse: int = 0,
+ condition: bool = False,
+ needRedstone: bool = True,
+ tickDelay: int = 0,
+ customName: str = "",
+ executeOnFirstTick: bool = False,
+ trackOutput: bool = True,
+):
+ """
+ 使用指定项目返回指定的指令方块放置指令项
+ :param command: `str`
+ 指令
+ :param particularValue:
+ 方块特殊值,即朝向
+ :0 下 无条件
+ :1 上 无条件
+ :2 z轴负方向 无条件
+ :3 z轴正方向 无条件
+ :4 x轴负方向 无条件
+ :5 x轴正方向 无条件
+ :6 下 无条件
+ :7 下 无条件
+
+ :8 下 有条件
+ :9 上 有条件
+ :10 z轴负方向 有条件
+ :11 z轴正方向 有条件
+ :12 x轴负方向 有条件
+ :13 x轴正方向 有条件
+ :14 下 有条件
+ :14 下 有条件
+ 注意!此处特殊值中的条件会被下面condition参数覆写
+ :param impluse: `int 0|1|2`
+ 方块类型
+ 0脉冲 1循环 2连锁
+ :param condition: `bool`
+ 是否有条件
+ :param needRedstone: `bool`
+ 是否需要红石
+ :param tickDelay: `int`
+ 执行延时
+ :param customName: `str`
+ 悬浮字
+ lastOutput: `str`
+ 上次输出字符串,注意此处需要留空
+ :param executeOnFirstTick: `bool`
+ 首刻执行(循环指令方块是否激活后立即执行,若为False,则从激活时起延迟后第一次执行)
+ :param trackOutput: `bool`
+ 是否输出
+
+ :return:str
+ """
+ block = b"\x24" + particularValue.to_bytes(2, byteorder="big", signed=False)
+
+ for i in [
+ impluse.to_bytes(4, byteorder="big", signed=False),
+ bytes(command, encoding="utf-8") + b"\x00",
+ bytes(customName, encoding="utf-8") + b"\x00",
+ bytes("", encoding="utf-8") + b"\x00",
+ tickDelay.to_bytes(4, byteorder="big", signed=True),
+ executeOnFirstTick.to_bytes(1, byteorder="big"),
+ trackOutput.to_bytes(1, byteorder="big"),
+ condition.to_bytes(1, byteorder="big"),
+ needRedstone.to_bytes(1, byteorder="big"),
+ ]:
+ block += i
+ return block
+
+
+def commands_to_BDX_bytes(
+ commands: list,
+ max_height: int = 64,
+):
+ """
+ :param commands: 指令列表(指令, 延迟)
+ :param max_height: 生成结构最大高度
+ :return 成功与否,成功返回(True,未经过压缩的源,结构占用大小),失败返回(False,str失败原因)
+ """
+
+ _sideLength = bottem_side_length_of_smallest_square_bottom_box(
+ len(commands), max_height
+ )
+ _bytes = b""
+
+ y_forward = True
+ z_forward = True
+
+ now_y = 0
+ now_z = 0
+ now_x = 0
+
+ for cmd, delay in commands:
+ impluse = 2
+ condition = False
+ needRedstone = False
+ tickDelay = delay
+ customName = ""
+ executeOnFirstTick = False
+ trackOutput = True
+ _bytes += form_command_block_in_BDX_bytes(
+ cmd,
+ (1 if y_forward else 0)
+ if (
+ ((now_y != 0) and (not y_forward))
+ or (y_forward and (now_y != (max_height - 1)))
+ )
+ else (3 if z_forward else 2)
+ if (
+ ((now_z != 0) and (not z_forward))
+ or (z_forward and (now_z != _sideLength - 1))
+ )
+ else 5,
+ impluse=impluse,
+ condition=condition,
+ needRedstone=needRedstone,
+ tickDelay=tickDelay,
+ customName=customName,
+ executeOnFirstTick=executeOnFirstTick,
+ trackOutput=trackOutput,
+ )
+
+ now_y += 1 if y_forward else -1
+
+ if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)):
+ now_y -= 1 if y_forward else -1
+
+ y_forward = not y_forward
+
+ now_z += 1 if z_forward else -1
+
+ if ((now_z >= _sideLength) and z_forward) or (
+ (now_z < 0) and (not z_forward)
+ ):
+ now_z -= 1 if z_forward else -1
+ z_forward = not z_forward
+ _bytes += bdx_key[x][1]
+ now_x += 1
+ else:
+ _bytes += bdx_key[z][int(z_forward)]
+
+ else:
+ _bytes += bdx_key[y][int(y_forward)]
+
+ return (
+ _bytes,
+ [
+ now_x + 1,
+ max_height if now_x or now_z else now_y,
+ _sideLength if now_x else now_z,
+ ],
+ [now_x, now_y, now_z],
+ )
+
+
diff --git a/Musicreater/plugin/common.py b/Musicreater/plugin/common.py
new file mode 100644
index 0000000..94684c0
--- /dev/null
+++ b/Musicreater/plugin/common.py
@@ -0,0 +1,14 @@
+'''
+存放通用的普遍性的内容
+'''
+
+import math
+
+
+def bottem_side_length_of_smallest_square_bottom_box(total: int, maxHeight: int):
+ """给定总方块数量和最大高度,返回所构成的图形外切正方形的边长
+ :param total: 总方块数量
+ :param maxHeight: 最大高度
+ :return: 外切正方形的边长 int"""
+ return math.ceil(math.sqrt(math.ceil(total / maxHeight)))
+
diff --git a/Musicreater/plugin/mcstructure.py b/Musicreater/plugin/mcstructure.py
new file mode 100644
index 0000000..33e13cb
--- /dev/null
+++ b/Musicreater/plugin/mcstructure.py
@@ -0,0 +1,237 @@
+'''
+存放有关MCSTRUCTURE结构操作的内容
+'''
+
+from .common import bottem_side_length_of_smallest_square_bottom_box
+from TrimMCStruct import Structure, Block, TAG_Long, TAG_Byte
+
+def form_note_block_in_NBT_struct(
+ note: int, coordinate: tuple, instrument: str = "note.harp", powered: bool = False
+):
+ """生成音符盒方块
+ :param note: `int`(0~24)
+ 音符的音高
+ :param coordinate: `tuple[int,int,int]`
+ 此方块所在之相对坐标
+ :param instrument: `str`
+ 音符盒的乐器
+ :param powered: `bool`
+ 是否已被激活
+ :return Block
+ """
+
+ return Block(
+ "minecraft",
+ "noteblock",
+ {
+ "instrument": instrument.replace("note.", ""),
+ "note": note,
+ "powered": powered,
+ },
+ {
+ "block_entity_data": {
+ "note": TAG_Byte(note),
+ "id": "noteblock",
+ "x": coordinate[0],
+ "y": coordinate[1],
+ "z": coordinate[2],
+ }
+ },
+ )
+
+
+def form_repeater_in_NBT_struct(
+ delay: int, facing: int
+):
+ """生成中继器方块
+ :param facing:
+ :param delay: 1~4
+ :return Block()"""
+
+
+ return Block(
+ "minecraft",
+ "unpowered_repeater",
+ {
+ "repeater_delay": delay,
+ "direction": facing,
+ },
+ )
+
+
+def form_command_block_in_NBT_struct(
+ command: str,
+ coordinate: tuple,
+ particularValue: int,
+ impluse: int = 0,
+ condition: bool = False,
+ alwaysRun: bool = True,
+ tickDelay: int = 0,
+ customName: str = "",
+ executeOnFirstTick: bool = False,
+ trackOutput: bool = True,
+):
+ """
+ 使用指定项目返回指定的指令方块结构
+ :param command: `str`
+ 指令
+ :param coordinate: `tuple[int,int,int]`
+ 此方块所在之相对坐标
+ :param particularValue:
+ 方块特殊值,即朝向
+ :0 下 无条件
+ :1 上 无条件
+ :2 z轴负方向 无条件
+ :3 z轴正方向 无条件
+ :4 x轴负方向 无条件
+ :5 x轴正方向 无条件
+ :6 下 无条件
+ :7 下 无条件
+
+ :8 下 有条件
+ :9 上 有条件
+ :10 z轴负方向 有条件
+ :11 z轴正方向 有条件
+ :12 x轴负方向 有条件
+ :13 x轴正方向 有条件
+ :14 下 有条件
+ :14 下 有条件
+ 注意!此处特殊值中的条件会被下面condition参数覆写
+ :param impluse: `int 0|1|2`
+ 方块类型
+ 0脉冲 1循环 2连锁
+ :param condition: `bool`
+ 是否有条件
+ :param alwaysRun: `bool`
+ 是否始终执行
+ :param tickDelay: `int`
+ 执行延时
+ :param customName: `str`
+ 悬浮字
+ :param executeOnFirstTick: `bool`
+ 首刻执行(循环指令方块是否激活后立即执行,若为False,则从激活时起延迟后第一次执行)
+ :param trackOutput: `bool`
+ 是否输出
+
+ :return:str
+ """
+
+
+ return Block(
+ "minecraft",
+ "command_block"
+ if impluse == 0
+ else ("repeating_command_block" if impluse == 1 else "chain_command_block"),
+ states={"conditional_bit": condition, "facing_direction": particularValue},
+ extra_data={
+ "block_entity_data": {
+ "Command": command,
+ "CustomName": customName,
+ "ExecuteOnFirstTick": executeOnFirstTick,
+ "LPCommandMode": 0,
+ "LPCondionalMode": False,
+ "LPRedstoneMode": False,
+ "LastExecution": TAG_Long(0),
+ "LastOutput": "",
+ "LastOutputParams": [],
+ "SuccessCount": 0,
+ "TickDelay": tickDelay,
+ "TrackOutput": trackOutput,
+ "Version": 25,
+ "auto": alwaysRun,
+ "conditionMet": False, # 是否已经满足条件
+ "conditionalMode": condition,
+ "id": "CommandBlock",
+ "isMovable": True,
+ "powered": False, # 是否已激活
+ "x": coordinate[0],
+ "y": coordinate[1],
+ "z": coordinate[2],
+ }
+ },
+ compability_version=17959425,
+ )
+
+
+def commands_to_structure(
+ commands: list,
+ max_height: int = 64,
+):
+ """
+ :param commands: 指令列表(指令, 延迟)
+ :param max_height: 生成结构最大高度
+ :return 成功与否,成功返回(结构类,结构占用大小),失败返回(False,str失败原因)
+ """
+
+
+ _sideLength = bottem_side_length_of_smallest_square_bottom_box(
+ len(commands), max_height
+ )
+
+ struct = Structure(
+ (_sideLength, max_height, _sideLength), # 声明结构大小
+ )
+
+ y_forward = True
+ z_forward = True
+
+ now_y = 0
+ now_z = 0
+ now_x = 0
+
+ for cmd, delay in commands:
+ coordinate = (now_x, now_y, now_z)
+ struct.set_block(
+ coordinate,
+ form_command_block_in_NBT_struct(
+ command=cmd,
+ coordinate=coordinate,
+ particularValue=(1 if y_forward else 0)
+ if (
+ ((now_y != 0) and (not y_forward))
+ or (y_forward and (now_y != (max_height - 1)))
+ )
+ else (
+ (3 if z_forward else 2)
+ if (
+ ((now_z != 0) and (not z_forward))
+ or (z_forward and (now_z != _sideLength - 1))
+ )
+ else 5
+ ),
+ impluse=2,
+ condition=False,
+ alwaysRun=True,
+ tickDelay=delay,
+ customName="",
+ executeOnFirstTick=False,
+ trackOutput=True,
+ ),
+ )
+
+ now_y += 1 if y_forward else -1
+
+ if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)):
+ now_y -= 1 if y_forward else -1
+
+ y_forward = not y_forward
+
+ now_z += 1 if z_forward else -1
+
+ if ((now_z >= _sideLength) and z_forward) or (
+ (now_z < 0) and (not z_forward)
+ ):
+ now_z -= 1 if z_forward else -1
+ z_forward = not z_forward
+ now_x += 1
+
+ return (
+ struct,
+ (
+ now_x + 1,
+ max_height if now_x or now_z else now_y,
+ _sideLength if now_x else now_z,
+ ),
+ (now_x, now_y, now_z),
+ )
+
diff --git a/Musicreater/previous.py b/Musicreater/previous.py
new file mode 100644
index 0000000..40e4c9c
--- /dev/null
+++ b/Musicreater/previous.py
@@ -0,0 +1,449 @@
+'''
+旧版本功能以及已经弃用的函数
+'''
+
+from .exceptions import *
+from .main import midiConvert
+
+
+def to_command_list_method1(
+ self: midiConvert,
+ scoreboard_name: str = "mscplay",
+ MaxVolume: float = 1.0,
+ speed: float = 1.0,
+) -> list:
+ """
+ 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表
+ :param scoreboard_name: 我的世界的计分板名称
+ :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
+ :return: tuple(命令列表, 命令个数, 计分板最大值)
+ """
+ # :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
+ tracks = []
+ if speed == 0:
+ if self.debug_mode:
+ raise ZeroSpeedError("播放速度仅可为正实数")
+ speed = 1
+ MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
+
+ commands = 0
+ maxscore = 0
+
+ # 分轨的思路其实并不好,但这个算法就是这样
+ # 所以我建议用第二个方法 _toCmdList_m2
+ for i, track in enumerate(self.midi.tracks):
+ ticks = 0
+ instrumentID = 0
+ singleTrack = []
+
+ for msg in track:
+ ticks += msg.time
+ if msg.is_meta:
+ if msg.type == "set_tempo":
+ tempo = msg.tempo
+ else:
+ if msg.type == "program_change":
+ instrumentID = msg.program
+
+ if msg.type == "note_on" and msg.velocity != 0:
+ try:
+ nowscore = round(
+ (ticks * tempo)
+ / ((self.midi.ticks_per_beat * float(speed)) * 50000)
+ )
+ except NameError:
+ raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
+ maxscore = max(maxscore, nowscore)
+ if msg.channel == 9:
+ soundID, _X = self.perc_inst_to_soundID_withX(instrumentID)
+ else:
+ soundID, _X = self.inst_to_souldID_withX(instrumentID)
+
+ singleTrack.append(
+ "execute @a[scores={"
+ + str(scoreboard_name)
+ + "="
+ + str(nowscore)
+ + "}"
+ + f"] ~ ~ ~ playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
+ f"{2 ** ((msg.note - 60 - _X) / 12)}"
+ )
+ commands += 1
+ if len(singleTrack) != 0:
+ tracks.append(singleTrack)
+
+ return [tracks, commands, maxscore]
+
+
+# 原本这个算法的转换效果应该和上面的算法相似的
+def _toCmdList_m2(
+ self: midiConvert,
+ scoreboard_name: str = "mscplay",
+ MaxVolume: float = 1.0,
+ speed: float = 1.0,
+) -> list:
+ """
+ 使用神羽和金羿的转换思路,将midi转换为我的世界命令列表
+ :param scoreboard_name: 我的世界的计分板名称
+ :param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
+ :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
+ :return: tuple(命令列表, 命令个数, 计分板最大值)
+ """
+
+ if speed == 0:
+ if self.debug_mode:
+ raise ZeroSpeedError("播放速度仅可为正实数")
+ speed = 1
+ MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
+
+ # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
+ channels = {
+ 0: [],
+ 1: [],
+ 2: [],
+ 3: [],
+ 4: [],
+ 5: [],
+ 6: [],
+ 7: [],
+ 8: [],
+ 9: [],
+ 10: [],
+ 11: [],
+ 12: [],
+ 13: [],
+ 14: [],
+ 15: [],
+ 16: [],
+ }
+
+ microseconds = 0
+
+ # 我们来用通道统计音乐信息
+ for msg in self.midi:
+ microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样
+ if not msg.is_meta:
+ if self.debug_mode:
+ try:
+ if msg.channel > 15:
+ raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
+ except AttributeError:
+ pass
+
+ if msg.type == "program_change":
+ channels[msg.channel].append(("PgmC", msg.program, microseconds))
+
+ elif msg.type == "note_on" and msg.velocity != 0:
+ channels[msg.channel].append(
+ ("NoteS", msg.note, msg.velocity, microseconds)
+ )
+
+ elif (msg.type == "note_on" and msg.velocity == 0) or (
+ msg.type == "note_off"
+ ):
+ channels[msg.channel].append(("NoteE", msg.note, microseconds))
+
+ """整合后的音乐通道格式
+ 每个通道包括若干消息元素其中逃不过这三种:
+
+ 1 切换乐器消息
+ ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
+
+ 2 音符开始消息
+ ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
+
+ 3 音符结束消息
+ ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
+
+ tracks = []
+ cmdAmount = 0
+ maxScore = 0
+
+ # 此处 我们把通道视为音轨
+ for i in channels.keys():
+ # 如果当前通道为空 则跳过
+ if not channels[i]:
+ continue
+
+ if i == 9:
+ SpecialBits = True
+ else:
+ SpecialBits = False
+
+ nowTrack = []
+
+ for msg in channels[i]:
+ if msg[0] == "PgmC":
+ InstID = msg[1]
+
+ elif msg[0] == "NoteS":
+ try:
+ soundID, _X = (
+ self.perc_inst_to_soundID_withX(InstID)
+ if SpecialBits
+ else self.inst_to_souldID_withX(InstID)
+ )
+ except UnboundLocalError as E:
+ if self.debug_mode:
+ raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
+ else:
+ soundID, _X = (
+ self.perc_inst_to_soundID_withX(-1)
+ if SpecialBits
+ else self.inst_to_souldID_withX(-1)
+ )
+ score_now = round(msg[-1] / float(speed) / 50)
+ maxScore = max(maxScore, score_now)
+
+ nowTrack.append(
+ self.execute_cmd_head.format(
+ "@a[scores=({}={})]".format(scoreboard_name, score_now)
+ .replace("(", r"{")
+ .replace(")", r"}")
+ )
+ + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
+ f"{2 ** ((msg[1] - 60 - _X) / 12)}"
+ )
+
+ cmdAmount += 1
+
+ if nowTrack:
+ tracks.append(nowTrack)
+
+ return [tracks, cmdAmount, maxScore]
+
+
+def _toCmdList_withDelay_m1(
+ self: midiConvert,
+ MaxVolume: float = 1.0,
+ speed: float = 1.0,
+ player: str = "@a",
+) -> list:
+ """
+ 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
+ :param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
+ :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
+ :param player: 玩家选择器,默认为`@a`
+ :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
+ """
+ tracks = {}
+
+ if speed == 0:
+ if self.debug_mode:
+ raise ZeroSpeedError("播放速度仅可为正实数")
+ speed = 1
+
+ MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
+
+ for i, track in enumerate(self.midi.tracks):
+ instrumentID = 0
+ ticks = 0
+
+ for msg in track:
+ ticks += msg.time
+ if msg.is_meta:
+ if msg.type == "set_tempo":
+ tempo = msg.tempo
+ else:
+ if msg.type == "program_change":
+ instrumentID = msg.program
+ if msg.type == "note_on" and msg.velocity != 0:
+ now_tick = round(
+ (ticks * tempo)
+ / ((self.midi.ticks_per_beat * float(speed)) * 50000)
+ )
+ soundID, _X = self.inst_to_souldID_withX(instrumentID)
+ try:
+ tracks[now_tick].append(
+ self.execute_cmd_head.format(player)
+ + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
+ f"{2 ** ((msg.note - 60 - _X) / 12)}"
+ )
+ except KeyError:
+ tracks[now_tick] = [
+ self.execute_cmd_head.format(player)
+ + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
+ f"{2 ** ((msg.note - 60 - _X) / 12)}"
+ ]
+
+ results = []
+
+ all_ticks = list(tracks.keys())
+ all_ticks.sort()
+
+ for i in range(len(all_ticks)):
+ if i != 0:
+ for j in range(len(tracks[all_ticks[i]])):
+ if j != 0:
+ results.append((tracks[all_ticks[i]][j], 0))
+ else:
+ results.append(
+ (tracks[all_ticks[i]][j], all_ticks[i] - all_ticks[i - 1])
+ )
+ else:
+ for j in range(len(tracks[all_ticks[i]])):
+ results.append((tracks[all_ticks[i]][j], all_ticks[i]))
+
+ return [results, max(all_ticks)]
+
+
+def _toCmdList_withDelay_m2(
+ self: midiConvert,
+ MaxVolume: float = 1.0,
+ speed: float = 1.0,
+ player: str = "@a",
+) -> list:
+ """
+ 使用神羽和金羿的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
+ :param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
+ :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
+ :param player: 玩家选择器,默认为`@a`
+ :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
+ """
+ tracks = {}
+ if speed == 0:
+ if self.debug_mode:
+ raise ZeroSpeedError("播放速度仅可为正实数")
+ speed = 1
+
+ MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
+
+ # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
+ channels = {
+ 0: [],
+ 1: [],
+ 2: [],
+ 3: [],
+ 4: [],
+ 5: [],
+ 6: [],
+ 7: [],
+ 8: [],
+ 9: [],
+ 10: [],
+ 11: [],
+ 12: [],
+ 13: [],
+ 14: [],
+ 15: [],
+ 16: [],
+ }
+
+ microseconds = 0
+
+ # 我们来用通道统计音乐信息
+ for msg in self.midi:
+ try:
+ microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样
+
+ # print(microseconds)
+ except NameError:
+ if self.debug_mode:
+ raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
+ else:
+ microseconds += (
+ msg.time * 1000
+ ) # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样
+
+ if msg.is_meta:
+ if msg.type == "set_tempo":
+ tempo = msg.tempo
+ else:
+ if self.debug_mode:
+ try:
+ if msg.channel > 15:
+ raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
+ except AttributeError:
+ pass
+
+ if msg.type == "program_change":
+ channels[msg.channel].append(("PgmC", msg.program, microseconds))
+
+ elif msg.type == "note_on" and msg.velocity != 0:
+ channels[msg.channel].append(
+ ("NoteS", msg.note, msg.velocity, microseconds)
+ )
+
+ elif (msg.type == "note_on" and msg.velocity == 0) or (
+ msg.type == "note_off"
+ ):
+ channels[msg.channel].append(("NoteE", msg.note, microseconds))
+
+ """整合后的音乐通道格式
+ 每个通道包括若干消息元素其中逃不过这三种:
+
+ 1 切换乐器消息
+ ("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
+
+ 2 音符开始消息
+ ("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
+
+ 3 音符结束消息
+ ("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
+
+ results = []
+
+ for i in channels.keys():
+ # 如果当前通道为空 则跳过
+ if not channels[i]:
+ continue
+
+ if i == 9:
+ SpecialBits = True
+ else:
+ SpecialBits = False
+
+ for msg in channels[i]:
+ if msg[0] == "PgmC":
+ InstID = msg[1]
+
+ elif msg[0] == "NoteS":
+ try:
+ soundID, _X = (
+ self.perc_inst_to_soundID_withX(InstID)
+ if SpecialBits
+ else self.inst_to_souldID_withX(InstID)
+ )
+ except UnboundLocalError as E:
+ if self.debug_mode:
+ raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
+ else:
+ soundID, _X = (
+ self.perc_inst_to_soundID_withX(-1)
+ if SpecialBits
+ else self.inst_to_souldID_withX(-1)
+ )
+ score_now = round(msg[-1] / float(speed) / 50)
+
+ try:
+ tracks[score_now].append(
+ self.execute_cmd_head.format(player)
+ + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
+ f"{2 ** ((msg[1] - 60 - _X) / 12)}"
+ )
+ except KeyError:
+ tracks[score_now] = [
+ self.execute_cmd_head.format(player)
+ + f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
+ f"{2 ** ((msg[1] - 60 - _X) / 12)}"
+ ]
+
+ all_ticks = list(tracks.keys())
+ all_ticks.sort()
+
+ for i in range(len(all_ticks)):
+ for j in range(len(tracks[all_ticks[i]])):
+ results.append(
+ (
+ tracks[all_ticks[i]][j],
+ (
+ 0
+ if j != 0
+ else (
+ all_ticks[i] - all_ticks[i - 1] if i != 0 else all_ticks[i]
+ )
+ ),
+ )
+ )
+
+ return [results, max(all_ticks)]
diff --git a/Musicreater/subclass.py b/Musicreater/subclass.py
new file mode 100644
index 0000000..6e29e63
--- /dev/null
+++ b/Musicreater/subclass.py
@@ -0,0 +1,91 @@
+'''
+存储许多非主要的相关类
+'''
+
+
+from dataclasses import dataclass
+from typing import TypeVar
+
+T = TypeVar("T") # Declare type variable
+
+
+@dataclass(init=False)
+class SingleNote:
+ instrument: int
+ """乐器编号"""
+ note: int
+ """音符编号"""
+ velocity: int
+ """力度/响度"""
+ startTime: int
+ """开始之时 ms"""
+ lastTime: int
+ """音符持续时间 ms"""
+
+ def __init__(
+ self, instrument: int, pitch: int, velocity: int, startTime: int, lastTime: int
+ ):
+ """用于存储单个音符的类
+ :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.startTime: int = startTime
+ """开始之时 ms"""
+ self.lastTime: int = lastTime
+ """音符持续时间 ms"""
+
+ @property
+ def inst(self):
+ """乐器编号"""
+ return self.instrument
+
+ @inst.setter
+ def inst(self, inst_):
+ self.instrument = inst_
+
+ @property
+ def pitch(self):
+ """音符编号"""
+ return self.note
+
+ def __str__(self):
+ return (
+ f"Note(inst = {self.inst}, pitch = {self.note}, velocity = {self.velocity}, "
+ f"startTime = {self.startTime}, lastTime = {self.lastTime}, )"
+ )
+
+ def __tuple__(self):
+ return self.inst, self.note, self.velocity, self.startTime, self.lastTime
+
+ def __dict__(self):
+ return {
+ "inst": self.inst,
+ "pitch": self.note,
+ "velocity": self.velocity,
+ "startTime": self.startTime,
+ "lastTime": self.lastTime,
+ }
+
+
+class MethodList(list):
+ """函数列表,列表中的所有元素均为函数"""
+
+ def __init__(self, in_=()):
+ """函数列表,列表中的所有元素均为函数"""
+ super().__init__()
+ self._T = [_x for _x in in_]
+
+ def __getitem__(self, item) -> T:
+ return self._T[item]
+
+ def __len__(self) -> int:
+ return self._T.__len__()
diff --git a/Musicreater/utils.py b/Musicreater/utils.py
index 5cb136a..d5b0971 100644
--- a/Musicreater/utils.py
+++ b/Musicreater/utils.py
@@ -1,459 +1,17 @@
-import math
-import os
-
-bdx_key = {
- "x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"],
- "y": [b"\x11", b"\x10", b"\x1d", b"\x16", b"\x17"],
- "z": [b"\x13", b"\x12", b"\x1e", b"\x18", b"\x19"],
-}
-"""key存储了方块移动指令的数据,其中可以用key[x|y|z][0|1]来表示xyz的减或增
-而key[][2+]是用来增加指定数目的"""
-
-x = "x"
-y = "y"
-z = "z"
+'''
+存放主程序所必须的功能性内容
+'''
-def bdx_move(axis: str, value: int):
- if value == 0:
- return b""
- if abs(value) == 1:
- return bdx_key[axis][0 if value == -1 else 1]
-
- pointer = sum(
- [
- 1 if i else 0
- for i in (
- value != -1,
- value < -1 or value > 1,
- value < -128 or value > 127,
- value < -32768 or value > 32767,
- )
- ]
- )
-
- return bdx_key[axis][pointer] + value.to_bytes(
- 2 ** (pointer - 2), "big", signed=True
- )
-
-
-def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
- """使用compression指定的算法打包目录为zip文件\n
- 默认算法为DEFLATED(8),可用算法如下:\n
- STORED = 0\n
- DEFLATED = 8\n
- BZIP2 = 12\n
- LZMA = 14\n
+def mctick2timestr(mc_tick: int):
"""
- import zipfile
-
- zipf = zipfile.ZipFile(outFilename, "w", compression)
- pre_len = len(os.path.dirname(sourceDir))
- for parent, dirnames, filenames in os.walk(sourceDir):
- for filename in filenames:
- if filename == exceptFile:
- continue
- pathfile = os.path.join(parent, filename)
- arc_name = pathfile[pre_len:].strip(os.path.sep) # 相对路径
- zipf.write(pathfile, arc_name)
- zipf.close()
-
-
-def form_command_block_in_BDX_bytes(
- command: str,
- particularValue: int,
- impluse: int = 0,
- condition: bool = False,
- needRedstone: bool = True,
- tickDelay: int = 0,
- customName: str = "",
- executeOnFirstTick: bool = False,
- trackOutput: bool = True,
-):
+ 将《我的世界》的游戏刻计转为表示时间的字符串
"""
- 使用指定项目返回指定的指令方块放置指令项
- :param command: `str`
- 指令
- :param particularValue:
- 方块特殊值,即朝向
- :0 下 无条件
- :1 上 无条件
- :2 z轴负方向 无条件
- :3 z轴正方向 无条件
- :4 x轴负方向 无条件
- :5 x轴正方向 无条件
- :6 下 无条件
- :7 下 无条件
+ return str(int(int(mc_tick / 20) / 60)) + ":" + str(int(int(mc_tick / 20) % 60))
- :8 下 有条件
- :9 上 有条件
- :10 z轴负方向 有条件
- :11 z轴正方向 有条件
- :12 x轴负方向 有条件
- :13 x轴正方向 有条件
- :14 下 有条件
- :14 下 有条件
- 注意!此处特殊值中的条件会被下面condition参数覆写
- :param impluse: `int 0|1|2`
- 方块类型
- 0脉冲 1循环 2连锁
- :param condition: `bool`
- 是否有条件
- :param needRedstone: `bool`
- 是否需要红石
- :param tickDelay: `int`
- 执行延时
- :param customName: `str`
- 悬浮字
- lastOutput: `str`
- 上次输出字符串,注意此处需要留空
- :param executeOnFirstTick: `bool`
- 首刻执行(循环指令方块是否激活后立即执行,若为False,则从激活时起延迟后第一次执行)
- :param trackOutput: `bool`
- 是否输出
- :return:str
+def empty_midi_channels(channel_count: int = 17) -> dict:
"""
- block = b"\x24" + particularValue.to_bytes(2, byteorder="big", signed=False)
-
- for i in [
- impluse.to_bytes(4, byteorder="big", signed=False),
- bytes(command, encoding="utf-8") + b"\x00",
- bytes(customName, encoding="utf-8") + b"\x00",
- bytes("", encoding="utf-8") + b"\x00",
- tickDelay.to_bytes(4, byteorder="big", signed=True),
- executeOnFirstTick.to_bytes(1, byteorder="big"),
- trackOutput.to_bytes(1, byteorder="big"),
- condition.to_bytes(1, byteorder="big"),
- needRedstone.to_bytes(1, byteorder="big"),
- ]:
- block += i
- return block
-
-
-def bottem_side_length_of_smallest_square_bottom_box(total: int, maxHeight: int):
- """给定总方块数量和最大高度,返回所构成的图形外切正方形的边长
- :param total: 总方块数量
- :param maxHeight: 最大高度
- :return: 外切正方形的边长 int"""
- return math.ceil(math.sqrt(math.ceil(total / maxHeight)))
-
-
-def commands_to_BDX_bytes(
- commands: list,
- max_height: int = 64,
-):
+ 空MIDI通道字典
"""
- :param commands: 指令列表(指令, 延迟)
- :param max_height: 生成结构最大高度
- :return 成功与否,成功返回(True,未经过压缩的源,结构占用大小),失败返回(False,str失败原因)
- """
-
- _sideLength = bottem_side_length_of_smallest_square_bottom_box(
- len(commands), max_height
- )
- _bytes = b""
-
- y_forward = True
- z_forward = True
-
- now_y = 0
- now_z = 0
- now_x = 0
-
- for cmd, delay in commands:
- impluse = 2
- condition = False
- needRedstone = False
- tickDelay = delay
- customName = ""
- executeOnFirstTick = False
- trackOutput = True
- _bytes += form_command_block_in_BDX_bytes(
- cmd,
- (1 if y_forward else 0)
- if (
- ((now_y != 0) and (not y_forward))
- or (y_forward and (now_y != (max_height - 1)))
- )
- else (3 if z_forward else 2)
- if (
- ((now_z != 0) and (not z_forward))
- or (z_forward and (now_z != _sideLength - 1))
- )
- else 5,
- impluse=impluse,
- condition=condition,
- needRedstone=needRedstone,
- tickDelay=tickDelay,
- customName=customName,
- executeOnFirstTick=executeOnFirstTick,
- trackOutput=trackOutput,
- )
-
- now_y += 1 if y_forward else -1
-
- if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)):
- now_y -= 1 if y_forward else -1
-
- y_forward = not y_forward
-
- now_z += 1 if z_forward else -1
-
- if ((now_z >= _sideLength) and z_forward) or (
- (now_z < 0) and (not z_forward)
- ):
- now_z -= 1 if z_forward else -1
- z_forward = not z_forward
- _bytes += bdx_key[x][1]
- now_x += 1
- else:
- _bytes += bdx_key[z][int(z_forward)]
-
- else:
- _bytes += bdx_key[y][int(y_forward)]
-
- return (
- _bytes,
- [
- now_x + 1,
- max_height if now_x or now_z else now_y,
- _sideLength if now_x else now_z,
- ],
- [now_x, now_y, now_z],
- )
-
-
-def form_note_block_in_NBT_struct(
- note: int, coordinate: tuple, instrument: str = "note.harp", powered: bool = False
-):
- """生成音符盒方块
- :param note: `int`(0~24)
- 音符的音高
- :param coordinate: `tuple[int,int,int]`
- 此方块所在之相对坐标
- :param instrument: `str`
- 音符盒的乐器
- :param powered: `bool`
- 是否已被激活
- :return Block
- """
-
- from TrimMCStruct import Block, TAG_Byte
- return Block(
- "minecraft",
- "noteblock",
- {
- "instrument": instrument.replace("note.", ""),
- "note": note,
- "powered": powered,
- },
- {
- "block_entity_data": {
- "note": TAG_Byte(note),
- "id": "noteblock",
- "x": coordinate[0],
- "y": coordinate[1],
- "z": coordinate[2],
- }
- },
- )
-
-
-def form_repeater_in_NBT_struct(
- delay: int, facing: int
-):
- """生成中继器方块
- :param facing:
- :param delay: 1~4
- :return Block()"""
-
- from TrimMCStruct import Block
-
- return Block(
- "minecraft",
- "unpowered_repeater",
- {
- "repeater_delay": delay,
- "direction": facing,
- },
- )
-
-
-def form_command_block_in_NBT_struct(
- command: str,
- coordinate: tuple,
- particularValue: int,
- impluse: int = 0,
- condition: bool = False,
- alwaysRun: bool = True,
- tickDelay: int = 0,
- customName: str = "",
- executeOnFirstTick: bool = False,
- trackOutput: bool = True,
-):
- """
- 使用指定项目返回指定的指令方块结构
- :param command: `str`
- 指令
- :param coordinate: `tuple[int,int,int]`
- 此方块所在之相对坐标
- :param particularValue:
- 方块特殊值,即朝向
- :0 下 无条件
- :1 上 无条件
- :2 z轴负方向 无条件
- :3 z轴正方向 无条件
- :4 x轴负方向 无条件
- :5 x轴正方向 无条件
- :6 下 无条件
- :7 下 无条件
-
- :8 下 有条件
- :9 上 有条件
- :10 z轴负方向 有条件
- :11 z轴正方向 有条件
- :12 x轴负方向 有条件
- :13 x轴正方向 有条件
- :14 下 有条件
- :14 下 有条件
- 注意!此处特殊值中的条件会被下面condition参数覆写
- :param impluse: `int 0|1|2`
- 方块类型
- 0脉冲 1循环 2连锁
- :param condition: `bool`
- 是否有条件
- :param alwaysRun: `bool`
- 是否始终执行
- :param tickDelay: `int`
- 执行延时
- :param customName: `str`
- 悬浮字
- :param executeOnFirstTick: `bool`
- 首刻执行(循环指令方块是否激活后立即执行,若为False,则从激活时起延迟后第一次执行)
- :param trackOutput: `bool`
- 是否输出
-
- :return:str
- """
-
- from TrimMCStruct import Block, TAG_Long
-
- return Block(
- "minecraft",
- "command_block"
- if impluse == 0
- else ("repeating_command_block" if impluse == 1 else "chain_command_block"),
- states={"conditional_bit": condition, "facing_direction": particularValue},
- extra_data={
- "block_entity_data": {
- "Command": command,
- "CustomName": customName,
- "ExecuteOnFirstTick": executeOnFirstTick,
- "LPCommandMode": 0,
- "LPCondionalMode": False,
- "LPRedstoneMode": False,
- "LastExecution": TAG_Long(0),
- "LastOutput": "",
- "LastOutputParams": [],
- "SuccessCount": 0,
- "TickDelay": tickDelay,
- "TrackOutput": trackOutput,
- "Version": 25,
- "auto": alwaysRun,
- "conditionMet": False, # 是否已经满足条件
- "conditionalMode": condition,
- "id": "CommandBlock",
- "isMovable": True,
- "powered": False, # 是否已激活
- "x": coordinate[0],
- "y": coordinate[1],
- "z": coordinate[2],
- }
- },
- compability_version=17959425,
- )
-
-
-def commands_to_structure(
- commands: list,
- max_height: int = 64,
-):
- """
- :param commands: 指令列表(指令, 延迟)
- :param max_height: 生成结构最大高度
- :return 成功与否,成功返回(结构类,结构占用大小),失败返回(False,str失败原因)
- """
-
- from TrimMCStruct import Structure
-
- _sideLength = bottem_side_length_of_smallest_square_bottom_box(
- len(commands), max_height
- )
-
- struct = Structure(
- (_sideLength, max_height, _sideLength), # 声明结构大小
- )
-
- y_forward = True
- z_forward = True
-
- now_y = 0
- now_z = 0
- now_x = 0
-
- for cmd, delay in commands:
- coordinate = (now_x, now_y, now_z)
- struct.set_block(
- coordinate,
- form_command_block_in_NBT_struct(
- command=cmd,
- coordinate=coordinate,
- particularValue=(1 if y_forward else 0)
- if (
- ((now_y != 0) and (not y_forward))
- or (y_forward and (now_y != (max_height - 1)))
- )
- else (
- (3 if z_forward else 2)
- if (
- ((now_z != 0) and (not z_forward))
- or (z_forward and (now_z != _sideLength - 1))
- )
- else 5
- ),
- impluse=2,
- condition=False,
- alwaysRun=True,
- tickDelay=delay,
- customName="",
- executeOnFirstTick=False,
- trackOutput=True,
- ),
- )
-
- now_y += 1 if y_forward else -1
-
- if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)):
- now_y -= 1 if y_forward else -1
-
- y_forward = not y_forward
-
- now_z += 1 if z_forward else -1
-
- if ((now_z >= _sideLength) and z_forward) or (
- (now_z < 0) and (not z_forward)
- ):
- now_z -= 1 if z_forward else -1
- z_forward = not z_forward
- now_x += 1
-
- return (
- struct,
- (
- now_x + 1,
- max_height if now_x or now_z else now_y,
- _sideLength if now_x else now_z,
- ),
- (now_x, now_y, now_z),
- )
+ return dict((i, {}) for i in range(channel_count))
diff --git a/README.md b/README.md
index 436320a..95d6e51 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-
音·创 Musicreater
@@ -8,7 +7,7 @@
-一款免费开源的 《我的世界》 MIDI音乐转换库。
+一款免费开源的《我的世界》数字音频转换库。
@@ -18,12 +17,8 @@
-
-
-
-
[![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
-[![][Bilibili: 诸葛亮与八卦阵]](https://space.bilibili.com/604072474)
+[![][Bilibili: 诸葛亮与八卦阵]](https://space.bilibili.com/604072474)
[![CodeStyle: black]](https://github.com/psf/black)
[![][python]](https://www.python.org/)
[![][license]](LICENSE)
@@ -34,96 +29,96 @@
[](https://github.com/TriM-Organization/Musicreater/stargazers)
[](https://github.com/TriM-Organization/Musicreater/forks)
+简体中文 🇨🇳 | [English🇬🇧](README_EN.md)
-简体中文🇨🇳 | [English🇬🇧](README_EN.md)
+## 介绍 🚀
-
-## 介绍🚀
-
-音·创 是一个免费开源的针对 **《我的世界》** 的MIDI音乐转换库
+音·创 是一个免费开源的针对 **《我的世界》** 的 MIDI 音乐转换库
欢迎加群:[861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
-## 下载安装
+## 安装 🔳
-- 使用pypi
- ```bash
- pip install Musicreater
- ```
+- 使用 pypi
+
+ ```bash
+ pip install Musicreater
+ ```
- 如果出现错误,可以尝试:
- ```bash
- pip install -i https://pypi.python.org/simple Musicreater
- ```
-- (对于开发者来说)升级:
- ```bash
- pip install -i https://pypi.python.org/simple Musicreater --upgrade
- ```
+ ```bash
+ pip install -i https://pypi.python.org/simple Musicreater
+ ```
+
+- 升级:
+
+ ```bash
+ pip install -i https://pypi.python.org/simple Musicreater --upgrade
+ ```
- 克隆仓库并安装
- ```bash
- git clone https://gitee.com/TriM-Organization/Musicreater.git
- cd Musicreater
- python setup.py install
- ```
+ ```bash
+ git clone https://gitee.com/TriM-Organization/Musicreater.git
+ cd Musicreater
+ python setup.py install
+ ```
-以上命令种 `python`、`pip` 请依照各个环境不同灵活更换,可能为`python3`或`pip3`之类。
+以上命令中 `python`、`pip` 请依照各个环境不同灵活更换,可能为`python3`或`pip3`之类。
-## 文档📄
+## 文档 📄
[生成文件的使用](./docs/%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md)
-[仓库API文档](./docs/%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)
+[仓库 API 文档](./docs/%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)
-## 作者✒
+## 作者 ✒
-金羿 Eilles:我的世界基岩版指令师,个人开发者,B站不知名UP主,江西在校高中生。
+金羿 Eilles:我的世界基岩版指令师,个人开发者,B 站不知名 UP 主,江西在校高中生。
诸葛亮与八卦阵 bgArray:我的世界基岩版玩家,喜欢编程和音乐,深圳初二学生。
-## 致谢🙏
+## 致谢 🙏
+
本致谢列表排名无顺序。
-- 感谢 **昀梦**\ 找出指令生成错误bug并指正
-- 感谢由 **Charlie_Ping “查理平”** 带来的BDX文件转换参考,以及MIDI-我的世界对应乐器参考表格
-- 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导,同时我们参考了他的BDXworkshop作为BDX结构编辑的参考
-- 感谢由 **[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”**\ 带来的midi音色解析以及转换指令的算法,我们将其改编并应用;同时,感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力,让我们在原本一骑绝尘的摸鱼道路上转向开发,希望他能考上一个理想的大学!
-- 感谢 **Touch “偷吃”**\ 提供的BDX导入测试支持,并对程序的改进提供了丰富的意见;同时也感谢他的不断尝试新的内容,使我们的排错更进一步
-- 感谢 **Mono**\ 反馈安装时的问题,辅助我们找到了视窗操作系统下的兼容性问题;感谢其反馈延迟播放器出现的重大问题,让我们得以修改全部延迟播放错误
-- 感谢 **Ammelia “艾米利亚”**\ 敦促我们进行新的功能开发,并为新功能提出了非常优秀的大量建议,以及提供的BDX导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
-- 感谢 **[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”** 对我们项目的支持与宣传,希望他能考的一所优秀的大学!
-- 感谢 **指令师_苦力怕 playjuice123**\为我们的程序找出错误,并提醒我们修复一个一直存在的大bug。
-- 感谢 **雷霆**\为我们的程序找出错误,并提醒修复bug。
+- 感谢 **昀梦**\ 找出指令生成错误 bug 并指正
+- 感谢由 **Charlie_Ping “查理平”** 带来的 BDX 文件转换参考,以及 MIDI-我的世界对应乐器 参考表格
+- 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导,同时我们参考了他的 BDXworkshop 作为 BDX 结构编辑的参考
+- 感谢由 **[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”**\ 带来的 midi 音色解析以及转换指令的算法,我们将其改编并应用;同时,感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力,让我们在原本一骑绝尘的摸鱼道路上转向开发,希望他能考上一个理想的大学!
+- 感谢 **Touch “偷吃”**\ 提供的 BDX 导入测试支持,并对程序的改进提供了丰富的意见;同时也感谢他的不断尝试新的内容,使我们的排错更进一步
+- 感谢 **Mono**\ 反馈安装时的问题,辅助我们找到了视窗操作系统下的兼容性问题;感谢其反馈延迟播放器出现的重大问题,让我们得以修改全部延迟播放错误;尤其感谢他对于我们的软件的大力宣传
+- 感谢 **Ammelia “艾米利亚”**\ 敦促我们进行新的功能开发,并为新功能提出了非常优秀的大量建议,以及提供的 BDX 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
+- 感谢 **[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”** 对我们项目的支持与宣传,希望他能考得一所优秀的大学!
+- 感谢 **指令师\_苦力怕 playjuice123**\为我们的程序找出错误,并提醒我们修复一个一直存在的大 bug。
+- 感谢 **雷霆**\用他那令所有开发者都大为光火的操作方法为我们的程序找出错误,并提醒修复 bug。
-> 感谢广大群友为此程序提供的测试等支持
+> 感谢广大群友为此程序提供的测试等支持
>
-> 若您对我们有所贡献但您的名字没有显示在此列表中,请联系我们!
+> 若您对我们有所贡献但您的名字没有显示在此列表中,请联系我们!
-## 联系📞
+## 联系 📞
-若遇到库中的问题,欢迎在[此](https://gitee.com/TriM-Organization/Musicreater/issues/new)提出你的issue。
+若遇到库中的问题,欢迎在[此](https://gitee.com/TriM-Organization/Musicreater/issues/new)提出你的 issue。
-如果需要与开发组进行交流,欢迎加入我们的[开发闲聊Q群](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)。
+如果需要与开发组进行交流,欢迎加入我们的[开发闲聊 Q 群](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)。
---------------------------------------------
+---
-此项目并非一个官方 《我的世界》(*Minecraft*)项目
+此项目并非一个官方 《我的世界》(_Minecraft_)项目
此项目不隶属或关联于 Mojang Studios 或 微软
-此项目亦不与 网易 相关
+此项目亦不隶属或关联于 网易
“Minecraft”是 Mojang Synergies AB 的商标,此项目中所有对于“我的世界”、“Minecraft”等相关称呼均为引用性使用
-* 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易网络科技发展有限公司
+- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易网络科技发展有限公司
NOT AN OFFICIAL MINECRAFT PRODUCT.
NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
-
-
-
+NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-00A1E7?style=for-the-badge
[Bilibili: 诸葛亮与八卦阵]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
diff --git a/README_EN.md b/README_EN.md
index 2a0d106..9a06c15 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -1,86 +1,131 @@
-音·创 Musicreater
+
+ 音·创 Musicreater
+
-
+
+
-a free open-source library of converting midi files into _Minecraft_ formats.
+A free open-source library of converting digital music files into Minecraft formats.
-
+
+
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
-[![][Bilibili: bgArray]](https://space.bilibili.com/604072474)
+[![][Bilibili: bgArray]](https://space.bilibili.com/604072474)
[![CodeStyle: black]](https://github.com/psf/black)
-![][python]
+[![][python]](https://www.python.org/)
[![][license]](LICENSE)
[![][release]](../../releases)
-[简体中文🇨🇳](README.md) | English🇬🇧
+[](https://gitee.com/TriM-Organization/Musicreater/stargazers)
+[](https://gitee.com/TriM-Organization/Musicreater/members)
+[](https://github.com/TriM-Organization/Musicreater/stargazers)
+[](https://github.com/TriM-Organization/Musicreater/forks)
-**Notice that the language translation of *Musicreater* may be a little SLOW.**
+[简体中文 🇨🇳](README.md) | English🇬🇧
+
+**Notice that the language translation of _Musicreater_ may be a little SLOW.**
## Introduction🚀
-Musicreater is a free open-source library used for converting midi file into formats that could be read in *Minecraft*.
+Musicreater is a free open-source library used for converting digital music files into formats that could be read in _Minecraft_.
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
+## Installation 🔳
+
+- Via pypi
+
+ ```bash
+ pip install Musicreater
+ ```
+
+- If not work, also try:
+ ```bash
+ pip install -i https://pypi.python.org/simple Musicreater
+ ```
+
+- Update:
+
+ ```bash
+ pip install -i https://pypi.python.org/simple Musicreater --upgrade
+ ```
+
+- Clone repo and Install:
+ ```bash
+ git clone https://github.com/TriM-Organization/Musicreater.git
+ cd Musicreater
+ python setup.py install
+ ```
+
+Commands such as `python`、`pip` could be changed to some like `python3` or `pip3` according to the difference of platforms.
+
+
## Documentation📄
(Not in English yet)
[生成文件的使用](./docs/%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md)
-[仓库API文档](./docs/%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)
+[仓库 API 文档](./docs/%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)
### Authors✒
-Eilles (金羿):A senior high school student, individual developer, unfamous Bilibili UPer, which knows a little about commands in *Minecraft: Bedrock Edition*
-
-bgArray "诸葛亮与八卦阵": A junior high school student, player of *Minecraft: Bedrock Edition*, which is a fan of music and programming.
+Eilles (金羿):A senior high school student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
+bgArray "诸葛亮与八卦阵": A junior high school student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
## Thanks🙏
+
This list is not in any order.
-- Thank *昀梦*\ for finding and correcting the bugs in the commands that *Musicreater* generated.
-- Thank *Charlie_Ping “查理平”* for bdx convert function for reference, and the chart that's used to convert the mid's instruments into Minecraft's instruments.
-- Thank *[CMA_2401PT](https://github.com/CMA2401PT)* for BDXWorkShop for reference of the .bdx structure's operation, and his guidance in some aspects of our development.
-- Thank *[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”* \ for his midi analysis algorithm brought to us, we had adapted it and made it applied in one of our working method; Also, thank him for the [WebConvertor](https://dislink.github.io/midi2bdx/) which brought us so much pressure and power to develop as well as update our projects better, instead of loaf on our project. We hope he can get into a good university as he wantted to!
-- Thank *Touch “偷吃”*\ for support of debugging and testing program and algorithm, as well his/her suggestions to the improvement of our project
-- Thank *Mono*\ for reporting problems while installing
-- Thank *Ammelia “艾米利亚”*\ for urging us to develop new functions, and put forward a lot of excellent suggestions for new functions, as well as the BDX file's importing test support provided, which has given a lot of practical theoretical support for our new Structure Generating Algorithm
-- 感谢 *[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”* for supporting and promoting our project
+- Thank _昀梦_\ for finding and correcting the bugs in the commands that _Musicreater_ generated.
+- Thank _Charlie_Ping “查理平”_ for the bdx convert function for reference, and the reference chart that's used to convert the mid's instruments into Minecraft's instruments.
+- Thank _[CMA_2401PT](https://github.com/CMA2401PT)_ for BDXWorkShop for reference of the .bdx structure's operation, and his guidance in some aspects of our development.
+- Thank _[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”_ \ for his midi analysis algorithm brought to us, we had adapted it and made it applied in one of our working method; Also, thank him for the [WebConvertor](https://dislink.github.io/midi2bdx/) which brought us so much pressure and power to develop as well as update our projects better, instead of loaf on our project. We hope he can get into a good university as he wantted to!
+- Thank _Touch “偷吃”_\ for support of debugging and testing program and algorithm, as well his/her suggestions to the improvement of our project
+- Thank _Mono_\ for reporting problems while installing
+- Thank _Ammelia “艾米利亚”_\ for urging us to develop new functions, and put forward a lot of excellent suggestions for new functions, as well as the BDX file's importing test support provided, which has given a lot of practical theoretical support for our new Structure Generating Algorithm
+- Thank _[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”_ for supporting and promoting our project. Hope he could get into a good university!
+- Thank **指令师\_苦力怕 playjuice123**\ for finding bugs within our code, and noticed us to repair a big problem.
+- Thank **雷霆**\ for his annoying and provoking operations which may awake some problems within the program by chance and reminding us to repair.
-> Thanks for a lot of groupmates's support and help
+> Thanks for a lot of groupmates' support and help
>
> If you have given contribution but haven't been in the list, please contact us!
## Contact Us📞
-Meet problems? Welcome to give out your issue [here](https://gitee.com/EillesWan/Musicreater/issues/new)!
+Meet problems? Welcome to give out your issue [here](https://github.com/EillesWan/Musicreater/issues/new)!
Want to get in contact of developers? Welcome to join our [Chat QQ group](https://jq.qq.com/?_wv=1027&k=hpeRxrYr).
-
---------------------------------------------
+---
NOT AN OFFICIAL MINECRAFT PRODUCT.
NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
-此项目并非一个官方 《我的世界》(*Minecraft*)项目
+NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
+
+此项目并非一个官方 《我的世界》(_Minecraft_)项目
此项目不隶属或关联于 Mojang Studios 或 微软
+此项目亦不隶属或关联于 网易 相关
+“Minecraft”是 Mojang Synergies AB 的商标,此项目中所有对于“我的世界”、“Minecraft”等相关称呼均为引用性使用
+
+- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易网络科技发展有限公司
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-00A1E7?style=for-the-badge
[Bilibili: bgArray]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
-[python]: https://img.shields.io/badge/python-3.6-AB70FF?style=for-the-badge
+[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
-[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge
\ No newline at end of file
+[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge
diff --git a/requirements.txt b/requirements.txt
index 11e2979..01e8753 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
-Brotli>=1.0.9
+LyricLib>=0.0.3
mido>=1.2.10