mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2025-09-21 19:56:23 +00:00
不要更新!不要更新!命运的齿轮尚未开始转动,前方的道路将会铺满沙尘。
This commit is contained in:
@ -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
358
Musicreater/experiment.py
Normal 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)]
|
@ -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
19
Musicreater/plugin/__init__.py
Normal file
19
Musicreater/plugin/__init__.py
Normal 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"))
|
26
Musicreater/plugin/archive.py
Normal file
26
Musicreater/plugin/archive.py
Normal 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
199
Musicreater/plugin/bdx.py
Normal 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],
|
||||
)
|
||||
|
||||
|
14
Musicreater/plugin/common.py
Normal file
14
Musicreater/plugin/common.py
Normal 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)))
|
||||
|
237
Musicreater/plugin/mcstructure.py
Normal file
237
Musicreater/plugin/mcstructure.py
Normal 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
449
Musicreater/previous.py
Normal 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
91
Musicreater/subclass.py
Normal 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__()
|
@ -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))
|
||||
|
Reference in New Issue
Block a user