不要更新!不要更新!命运的齿轮尚未开始转动,前方的道路将会铺满沙尘。

This commit is contained in:
EillesWan 2023-05-07 19:25:51 +08:00
parent 42e2fcdd98
commit ffe5837c9f
15 changed files with 1726 additions and 1418 deletions

View File

@ -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音高:音符盒音调"""

358
Musicreater/experiment.py Normal file
View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

199
Musicreater/plugin/bdx.py Normal file
View File

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

View File

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

View File

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

449
Musicreater/previous.py Normal file
View File

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

91
Musicreater/subclass.py Normal file
View File

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

View File

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

109
README.md
View File

@ -1,4 +1,3 @@
<h1 align="center">
· Musicreater
</h1>
@ -8,7 +7,7 @@
</img>
</p>
<h3 align="center">一款免费开源的 我的世界 MIDI音乐转换库</h3>
<h3 align="center">一款免费开源的我的世界数字音频转换库</h3>
<p align="center">
<img src="https://forthebadge.com/images/badges/built-with-love.svg">
@ -18,12 +17,8 @@
</a>
<p>
[![][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 @@
[![GitHub Repo stars](https://img.shields.io/github/stars/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/stargazers)
[![GitHub Repo Forks](https://img.shields.io/github/forks/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](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我的世界基岩版玩家喜欢编程和音乐深圳初二学生
## 致谢🙏
## 致谢 🙏
本致谢列表排名无顺序
- 感谢 **昀梦**\<QQ1515399885\> 找出指令生成错误bug并指正
- 感谢由 **Charlie_Ping 查理平** 带来的BDX文件转换参考以及MIDI-我的世界对应乐器参考表格
- 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导同时我们参考了他的BDXworkshop作为BDX结构编辑的参考
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎**\<QQ1600515314\> 带来的midi音色解析以及转换指令的算法我们将其改编并应用同时感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力让我们在原本一骑绝尘的摸鱼道路上转向开发希望他能考上一个理想的大学
- 感谢 **Touch 偷吃**\<QQ1793537164\> 提供的BDX导入测试支持并对程序的改进提供了丰富的意见同时也感谢他的不断尝试新的内容使我们的排错更进一步
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题辅助我们找到了视窗操作系统下的兼容性问题感谢其反馈延迟播放器出现的重大问题让我们得以修改全部延迟播放错误
- 感谢 **Ammelia 艾米利亚**\<QQ2838334637\> 敦促我们进行新的功能开发并为新功能提出了非常优秀的大量建议以及提供的BDX导入测试支持为我们的新结构生成算法提供了大量的实际理论支持
- 感谢 **[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)** 对我们项目的支持与宣传希望他能考一所优秀的大学
- 感谢 **指令师_苦力怕 playjuice123**\<QQ240667197\>为我们的程序找出错误并提醒我们修复一个一直存在的大bug
- 感谢 **雷霆**\<QQ3555268519\>为我们的程序找出错误并提醒修复bug
- 感谢 **昀梦**\<QQ1515399885\> 找出指令生成错误 bug 并指正
- 感谢由 **Charlie_Ping 查理平** 带来的 BDX 文件转换参考以及 MIDI-我的世界对应乐器 参考表格
- 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导同时我们参考了他的 BDXworkshop 作为 BDX 结构编辑的参考
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法我们将其改编并应用同时感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力让我们在原本一骑绝尘的摸鱼道路上转向开发希望他能考上一个理想的大学
- 感谢 **Touch 偷吃**\<QQ1793537164\> 提供的 BDX 导入测试支持并对程序的改进提供了丰富的意见同时也感谢他的不断尝试新的内容使我们的排错更进一步
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题辅助我们找到了视窗操作系统下的兼容性问题感谢其反馈延迟播放器出现的重大问题让我们得以修改全部延迟播放错误尤其感谢他对于我们的软件的大力宣传
- 感谢 **Ammelia 艾米利亚**\<QQ2838334637\> 敦促我们进行新的功能开发并为新功能提出了非常优秀的大量建议以及提供的 BDX 导入测试支持为我们的新结构生成算法提供了大量的实际理论支持
- 感谢 **[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)** 对我们项目的支持与宣传希望他能考一所优秀的大学
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\>为我们的程序找出错误并提醒我们修复一个一直存在的大 bug
- 感谢 **雷霆**\<QQ3555268519\>用他那令所有开发者都大为光火的操作方法为我们的程序找出错误并提醒修复 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

View File

@ -1,86 +1,131 @@
<h1 align="center">· Musicreater</h1>
<h1 align="center">
· Musicreater
</h1>
<p align="center">
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
</img>
</p>
<h3 align="center">a free open-source library of converting midi files into _Minecraft_ formats.</h3>
<h3 align="center">A free open-source library of converting digital music files into <i>Minecraft</i> formats.</h3>
<p align="center">
<img src="https://forthebadge.com/images/badges/built-with-love.svg">
<img src="https://forthebadge.com/images/badges/built-with-love.svg">
</img>
<p>
[![][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🇬🇧
[![GiteeStar](https://gitee.com/TriM-Organization/Musicreater/badge/star.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/stargazers)
[![GiteeFork](https://gitee.com/TriM-Organization/Musicreater/badge/fork.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/members)
[![GitHub Repo stars](https://img.shields.io/github/stars/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/stargazers)
[![GitHub Repo Forks](https://img.shields.io/github/forks/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](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 *昀梦*\<QQ1515399885\> 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) 断联·斯福尔扎* \<QQ1600515314\> 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 偷吃*\<QQ1793537164\> for support of debugging and testing program and algorithm, as well his/her suggestions to the improvement of our project
- Thank *Mono*\<QQ738893087\> for reporting problems while installing
- Thank *Ammelia 艾米利亚*\<QQ2838334637\> 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 _昀梦_\<QQ1515399885\> 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) 断联·斯福尔扎_ \<QQ1600515314\> 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 偷吃_\<QQ1793537164\> for support of debugging and testing program and algorithm, as well his/her suggestions to the improvement of our project
- Thank _Mono_\<QQ738893087\> for reporting problems while installing
- Thank _Ammelia 艾米利亚_\<QQ2838334637\> 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**\<QQ240667197\> for finding bugs within our code, and noticed us to repair a big problem.
- Thank **雷霆**\<QQ3555268519\> 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
[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge

View File

@ -1,2 +1,2 @@
Brotli>=1.0.9
LyricLib>=0.0.3
mido>=1.2.10