13 Commits
nbs ... v1.4.0

47 changed files with 4367 additions and 2578 deletions

1
.gitignore vendored
View File

@@ -16,6 +16,7 @@
/llc_cli.py /llc_cli.py
/utils /utils
test.py test.py
RES.txt
# Byte-compiled / optimized # Byte-compiled / optimized
__pycache__/ __pycache__/

View File

@@ -3,7 +3,7 @@
1. ·创的全部开发者享有其完整版权其开发者可以在任一时刻终止以后音·创源代码开放若经由其开发者授予特殊权利则授权对象可以将源代码进行特定的被特殊授权的操作 1. ·创的全部开发者享有其完整版权其开发者可以在任一时刻终止以后音·创源代码开放若经由其开发者授予特殊权利则授权对象可以将源代码进行特定的被特殊授权的操作
2. ·创或其代码允许在 Apache2.0 协议的条款与说明下进行非商业使用 2. ·创或其代码允许在 Apache2.0 协议的条款与说明下进行非商业使用
3. 除部分代码特殊声明外·创允许对其或其代码进行商业化使用但是需要经过音·创主要开发者诸葛亮与八卦阵金羿的一致授权同时授权对象在商业化授权的使用过程中必须依照 Apache2.0 协议的条款与说明 3. 除部分代码特殊声明外·创允许对其或其代码进行商业化使用但是需要经过音·创主要开发者诸葛亮与八卦阵金羿的一致授权同时授权对象在商业化授权的使用过程中必须依照 Apache2.0 协议的条款与说明
4. 若存在对于音·创包含的部分代码的特殊开源声明则此部分代码依照其特定的开源方式授权但若此部分代码经由此部分代码的主要开发者一致特殊授权后商用则授权对象在商用时依照此部分的开发者所准许的方式或条款进行商用或默认依照 Apache2.0 协议进行商业化使用 4. 若存在对于音·创包含的部分代码的特殊开源声明则此部分代码依照其特定的开源方式授权但若此部分代码经由此部分代码的主要开发者一致特殊授权后商用则授权对象在商用时依照此部分的开发者所准许的方式或条款进行商用
5. Apache2.0 协议的英文原文副本可见下文 5. Apache2.0 协议的英文原文副本可见下文
> The English Translation of the TERMS AND CONDITIONS above is listed below > The English Translation of the TERMS AND CONDITIONS above is listed below
@@ -203,7 +203,7 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
Copyright 2022 Team-Ryoun 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") Copyright 2022 TriM-Organization 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & all the developers of Musicreater
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -8,17 +8,25 @@ A free open source library used for convert midi file into formats that is suita
版权所有 © 2023 音·创 开发者 版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater Copyright © 2023 all the developers of Musicreater
开源相关声明请见 ../License.md 开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: ../License.md Terms & Conditions: License.md in the root directory
""" """
# 睿穆组织 开发交流群 861684859 # 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com # Email TriM-Organization@hotmail.com
# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from .main import *
__version__ = "0.5.1" __version__ = "1.4.0"
__all__ = [] __vername__ = "红石指令音乐的生成"
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon")) __author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon"))
__all__ = [
# 主要类
"MidiConvert",
# 附加类
# "SingleNote",
"SingleCommand",
# "TimeStamp", 未来功能
]
from .main import *

View File

@@ -1,27 +1,75 @@
pitched_instrument_list = { # -*- coding: utf-8 -*-
from typing import Dict, List, Tuple
"""
存放常量与数值性内容
"""
"""
版权所有 © 2023 · 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
x = "x"
"""
x
"""
y = "y"
"""
y
"""
z = "z"
"""
z
"""
DEFAULT_PROGRESSBAR_STYLE = (
r"%%N [ %%s/%^s %%% __________ %%t|%^t ]",
("§e=§r", "§7=§r"),
)
"""
默认的进度条样式组
"""
# 以下是由 Touch “偷吃” 带来的高准确率音效对照表
# 包括乐音乐器对照和打击乐器对照
PITCHED_INSTRUMENT_TABLE: Dict[int, Tuple[str, int]] = {
0: ("note.harp", 6), 0: ("note.harp", 6),
1: ("note.harp", 6), 1: ("note.harp", 6),
2: ("note.pling", 6), 2: ("note.pling", 6),
3: ("note.harp", 6), 3: ("note.harp", 6),
4: ("note.pling", 6), 4: ("note.pling", 6),
5: ("note.pling", 6), 5: ("note.pling", 6),
6: ("note.harp", 6), 6: ("note.guitar", 7),
7: ("note.harp", 6), 7: ("note.harp", 6),
8: ("note.share", 7), # 打击乐器无音域 8: ("note.bell", 4), # 打击乐器无音域
9: ("note.harp", 6), 9: ("note.bell", 4),
10: ("note.didgeridoo", 8), 10: ("note.iron_xylophone", 6),
11: ("note.harp", 6), 11: ("note.iron_xylophone", 6),
12: ("note.xylophone", 4), 12: ("note.iron_xylophone", 6),
13: ("note.chime", 4), 13: ("note.xylophone", 4),
14: ("note.harp", 6), 14: ("note.chime", 4),
15: ("note.harp", 6), 15: ("note.harp", 6),
16: ("note.bass", 8), 16: ("note.bass", 8),
17: ("note.harp", 6), 17: ("note.harp", 6),
18: ("note.harp", 6), 18: ("note.flute", 5),
19: ("note.harp", 6), 19: ("note.harp", 6),
20: ("note.harp", 6), 20: ("note.harp", 6),
21: ("note.harp", 6), 21: ("note.flute", 5),
22: ("note.harp", 6), 22: ("note.flute", 5),
23: ("note.guitar", 7), 23: ("note.guitar", 7),
24: ("note.guitar", 7), 24: ("note.guitar", 7),
25: ("note.guitar", 7), 25: ("note.guitar", 7),
@@ -39,21 +87,21 @@ pitched_instrument_list = {
37: ("note.bass", 8), 37: ("note.bass", 8),
38: ("note.bass", 8), 38: ("note.bass", 8),
39: ("note.bass", 8), 39: ("note.bass", 8),
40: ("note.harp", 6), 40: ("note.flute", 5),
41: ("note.harp", 6), 41: ("note.flute", 5),
42: ("note.harp", 6), 42: ("note.flute", 5),
43: ("note.harp", 6), 43: ("note.flute", 5),
44: ("note.iron_xylophone", 6), 44: ("note.iron_xylophone", 6),
45: ("note.guitar", 7), 45: ("note.guitar", 7),
46: ("note.harp", 6), 46: ("note.harp", 6),
47: ("note.harp", 6), 47: ("note.bd", 7),
48: ("note.guitar", 7), 48: ("note.guitar", 7),
49: ("note.guitar", 7), 49: ("note.guitar", 7),
50: ("note.bit", 6), 50: ("note.bit", 6),
51: ("note.bit", 6), 51: ("note.bit", 6),
52: ("note.harp", 6), 52: ("note.flute", 5),
53: ("note.harp", 6), 53: ("note.flute", 5),
54: ("note.bit", 6), 54: ("note.flute", 5),
55: ("note.flute", 5), 55: ("note.flute", 5),
56: ("note.flute", 5), 56: ("note.flute", 5),
57: ("note.flute", 5), 57: ("note.flute", 5),
@@ -68,16 +116,16 @@ pitched_instrument_list = {
66: ("note.bit", 6), 66: ("note.bit", 6),
67: ("note.bit", 6), 67: ("note.bit", 6),
68: ("note.flute", 5), 68: ("note.flute", 5),
69: ("note.harp", 6), 69: ("note.bit", 6),
70: ("note.harp", 6), 70: ("note.banjo", 6),
71: ("note.flute", 5), 71: ("note.flute", 5),
72: ("note.flute", 5), 72: ("note.flute", 5),
73: ("note.flute", 5), 73: ("note.flute", 5),
74: ("note.harp", 6), 74: ("note.flute", 5),
75: ("note.flute", 5), 75: ("note.flute", 5),
76: ("note.harp", 6), 76: ("note.harp", 6),
77: ("note.harp", 6), 77: ("note.harp", 6),
78: ("note.harp", 6), 78: ("note.flute", 5),
79: ("note.harp", 6), 79: ("note.harp", 6),
80: ("note.bit", 6), 80: ("note.bit", 6),
81: ("note.bit", 6), 81: ("note.bit", 6),
@@ -107,35 +155,35 @@ pitched_instrument_list = {
105: ("note.banjo", 6), 105: ("note.banjo", 6),
106: ("note.harp", 6), 106: ("note.harp", 6),
107: ("note.harp", 6), 107: ("note.harp", 6),
108: ("note.harp", 6), 108: ("note.bell", 4),
109: ("note.harp", 6), 109: ("note.flute", 5),
110: ("note.harp", 6), 110: ("note.flute", 5),
111: ("note.guitar", 7), 111: ("note.guitar", 7),
112: ("note.harp", 6), 112: ("note.bell", 4),
113: ("note.bell", 4), 113: ("note.bell", 4),
114: ("note.harp", 6), 114: ("note.flute", 5),
115: ("note.cow_bell", 5), 115: ("note.cow_bell", 5),
116: ("note.bd", 7), # 打击乐器无音域 116: ("note.bd", 7), # 打击乐器无音域
117: ("note.bass", 8), 117: ("note.bass", 8),
118: ("note.bit", 6), 118: ("note.bit", 6),
119: ("note.bd", 7), # 打击乐器无音域 119: ("firework.blast", 7), # 打击乐器无音域
120: ("note.guitar", 7), 120: ("note.guitar", 7),
121: ("note.harp", 6), 121: ("note.harp", 6),
122: ("note.harp", 6), 122: ("note.harp", 6),
123: ("note.harp", 6), 123: ("note.harp", 6),
124: ("note.harp", 6), 124: ("note.harp", 6),
125: ("note.hat", 7), # 打击乐器无音域 125: ("note.hat", 7), # 打击乐器无音域
126: ("note.bd", 7), # 打击乐器无音域 126: ("firework.twinkle", 7), # 打击乐器无音域
127: ("note.snare", 7), # 打击乐器无音域 127: ("note.snare", 7), # 打击乐器无音域
} }
percussion_instrument_list = { PERCUSSION_INSTRUMENT_TABLE: Dict[int, Tuple[str, int]] = {
34: ("note.bd", 7), 34: ("note.bd", 7),
35: ("note.bd", 7), 35: ("note.bd", 7),
36: ("note.hat", 7), 36: ("note.hat", 7),
37: ("note.snare", 7), 37: ("note.snare", 7),
38: ("note.snare", 7), 38: ("note.snare", 7),
39: ("note.snare", 7), 39: ("fire.ignite", 7),
40: ("note.hat", 7), 40: ("note.hat", 7),
41: ("note.snare", 7), 41: ("note.snare", 7),
42: ("note.hat", 7), 42: ("note.hat", 7),
@@ -179,11 +227,22 @@ percussion_instrument_list = {
80: ("note.bell", 4), 80: ("note.bell", 4),
} }
instrument_to_blocks_list = { PERCUSSION_INSTRUMENT_LIST: List[str] = [
"note.snare",
"note.bd",
"note.hat",
"note.basedrum",
"firework.blast",
"firework.twinkle",
"fire.ignite",
]
INSTRUMENT_BLOCKS_TABLE: Dict[str, Tuple[str]] = {
"note.bass": ("planks",), "note.bass": ("planks",),
"note.snare": ("sand",), "note.snare": ("sand",),
"note.hat": ("glass",), "note.hat": ("glass",),
"note.bd": ("stone",), "note.bd": ("stone",),
"note.basedrum": ("stone",),
"note.bell": ("gold_block",), "note.bell": ("gold_block",),
"note.flute": ("clay",), "note.flute": ("clay",),
"note.chime": ("packed_ice",), "note.chime": ("packed_ice",),
@@ -196,5 +255,41 @@ instrument_to_blocks_list = {
"note.banjo": ("hay_block",), "note.banjo": ("hay_block",),
"note.pling": ("glowstone",), "note.pling": ("glowstone",),
"note.bassattack": ("command_block",), # 无法找到此音效 "note.bassattack": ("command_block",), # 无法找到此音效
"note.harp": ("glass",), "note.harp": ("dirt",),
# 呃……
"firework.blast": ("sandstone",),
"firework.twinkle": ("red_sandstone",),
"fire.ignite": ("concrete_powder",),
} }
# 即将启用
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音高:音符盒音调"""

View File

@@ -1,25 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
存放一堆报错类型
"""
# 睿穆组织 开发交流群 861684859 """
# Email TriM-Organization@hotmail.com
# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
"""一个简单的我的世界音频转换库
音·创 (Musicreater)
是一款免费开源的针对《我的世界》的midi音乐转换库
Musicreater(音·创)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
版权所有 © 2023 音·创 开发者 版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater Copyright © 2023 all the developers of Musicreater
开源相关声明请见 ../License.md 开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: ../License.md Terms & Conditions: License.md in the root directory
""" """
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
class MSCTBaseException(Exception): class MSCTBaseException(Exception):
"""音·创库版本的所有错误均继承于此""" """音·创库版本的所有错误均继承于此"""
@@ -54,6 +50,14 @@ class MidiDestroyedError(MSCTBaseException):
super().__init__("MIDI文件损坏无法读取MIDI文件", *args) super().__init__("MIDI文件损坏无法读取MIDI文件", *args)
class MidiUnboundError(MSCTBaseException):
"""未定义Midi对象"""
def __init__(self, *args):
"""未绑定Midi对象"""
super().__init__("未定义MidiFile对象你甚至没有对象就想要生孩子", *args)
class CommandFormatError(RuntimeError): class CommandFormatError(RuntimeError):
"""指令格式与目标格式不匹配而引起的错误""" """指令格式与目标格式不匹配而引起的错误"""

623
Musicreater/experiment.py Normal file
View File

@@ -0,0 +1,623 @@
# -*- coding: utf-8 -*-
"""
新版本功能以及即将启用的函数
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import random
from typing import Dict, List, Tuple, Union
from .constants import INSTRUMENT_BLOCKS_TABLE
from .exceptions import *
from .main import MidiConvert
from .subclass import *
from .utils import *
class FutureMidiConvertRSNB(MidiConvert):
"""
加入红石音乐适配
"""
music_command_list: Dict[int, SingleNoteBox]
"""音乐指令列表"""
@staticmethod
def soundID_to_block(sound_id: str, random_select: bool = False) -> str:
"""
将我的世界乐器名改作音符盒所需的对应方块名称
Parameters
----------
sound_id: str
将我的世界乐器名
random_select: bool
是否随机选取对应方块
Returns
-------
str方块名称
"""
try:
if random_select:
return random.choice(INSTRUMENT_BLOCKS_TABLE[sound_id])
else:
return INSTRUMENT_BLOCKS_TABLE[sound_id][0]
except KeyError:
return "air"
def to_note_list_in_delay(
self,
) -> Tuple[Dict[int, SingleNoteBox], int]:
"""
使用金羿的转换思路将midi转换为我的世界音符盒音高、乐器及延迟列表并输出每个音符之后的延迟
Returns
-------
tuple( Dict[int, SingleNoteBox], int音乐时长游戏刻 )
"""
self.to_music_channels()
tracks = {}
note_range = {}
InstID = -1
# cmd_amount = 0
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
for track_no, track in self.channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
# block_id = self.soundID_to_block((
# self.perc_inst_to_soundID_withX(msg[1])
# if SpecialBits
# else self.inst_to_souldID_withX(InstID)
# )[0])
# delaytime_now = round(msg[-1] / 50)
note_ = 2 ** ((msg[1] - 60) / 12)
try:
tracks[msg[-1]].append(
(
InstID,
note_,
)
)
except KeyError:
tracks[msg[-1]] = [(InstID, note_)]
try:
note_range[InstID]["max"] = max(
note_range[InstID]["max"], note_
)
note_range[InstID]["min"] = min(
note_range[InstID]["min"], note_
)
except KeyError:
note_range[InstID] = {"max": note_, "min": note_}
del InstID
all_ticks = list(tracks.keys())
all_ticks.sort()
results = []
print(note_range)
exit()
for i in range(len(all_ticks)):
for j in range(len(tracks[all_ticks[i]])):
results.append(
SingleCommand(
tracks[all_ticks[i]][j],
tick_delay=(
0
if j != 0
else (
all_ticks[i] - all_ticks[i - 1]
if i != 0
else all_ticks[i]
)
),
annotation="{}播放{}%{}".format(
mctick2timestr(i), max_volume * 100, ""
),
)
)
self.music_command_list = results
self.music_tick_num = max(all_ticks)
return results, self.music_tick_num
class FutureMidiConvertM4(MidiConvert):
"""
加入插值算法优化音感
: 经测试,生成效果已经达到,感觉良好
"""
# 临时用的插值计算函数
@staticmethod
def _linear_note(
_note: SingleNote,
_apply_time_division: int = 100,
) -> List[Tuple[int, int, int, int, float],]:
"""传入音符数据,返回以半秒为分割的插值列表
:param _note: SingleNote 音符
:return list[tuple(int开始时间毫秒, int乐器, int音符, int力度内置, float音量播放),]"""
totalCount = int(_note.duration / _apply_time_division)
if totalCount == 0:
return [
(_note.start_time, _note.inst, _note.pitch, _note.velocity, 1),
]
# print(totalCount)
result: List[Tuple[int, int, int, int, float],] = []
for _i in range(totalCount):
result.append(
(
_note.start_time + _i * _apply_time_division,
_note.instrument,
_note.pitch,
_note.velocity,
((totalCount - _i) / totalCount),
)
)
return result
# 简单的单音填充
def to_command_list_in_score(
self,
scoreboard_name: str = "mscplay",
max_volume: float = 1.0,
speed: float = 1.0,
) -> Tuple[List[List[SingleCommand]], int, int]:
"""
使用金羿的转换思路使用完全填充算法优化音感后将midi转换为我的世界命令列表
Parameters
----------
scoreboard_name: str
我的世界的计分板名称
max_volume: float
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
Returns
-------
tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
self.to_music_channels()
note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[])
InstID = -1
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# nowChannel = []
for track_no, track in self.channels[i].items():
noteMsgs = []
MsgIndex = []
for msg in track:
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],
track_number=track_no,
)
)
noteMsgs.pop(MsgIndex.index(msg[1]))
MsgIndex.pop(MsgIndex.index(msg[1]))
del InstID
tracks = []
cmd_amount = 0
max_score = 0
# 此处 我们把通道视为音轨
for no, track in note_channels.items():
# 如果当前通道为空 则跳过
if not track:
continue
SpecialBits = True if no == 9 else False
track_now = []
for note in track:
for every_note in self._linear_note(
note, 50 if note.track_no == 0 else 500
):
soundID, _X = (
self.perc_inst_to_soundID_withX(note.pitch)
if SpecialBits
else self.inst_to_souldID_withX(note.inst)
)
score_now = round(every_note[0] / speed / 50)
max_score = max(max_score, score_now)
mc_pitch = 2 ** ((note.pitch - 60 - _X) / 12)
blockmeter = (
1
/ (1 if note.track_no == 0 else 0.9)
/ max_volume
/ every_note[4]
- 1
)
track_now.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores=({}={})]".format(scoreboard_name, score_now)
.replace("(", r"{")
.replace(")", r"}")
)
+ "playsound {} @s ^ ^ ^{} {} {}".format(
soundID,
blockmeter,
note.velocity / 128,
"" if SpecialBits else mc_pitch,
),
annotation="{}播放{}%({}BM)的{}".format(
mctick2timestr(score_now),
max_volume * 100,
blockmeter,
"{}:{}".format(soundID, note.pitch),
),
),
)
cmd_amount += 1
if track_now:
self.music_command_list.extend(track_now)
tracks.append(track_now)
self.music_tick_num = max_score
return (tracks, cmd_amount, max_score)
# 简单的单音填充的延迟应用
def to_command_list_in_delay(
self,
max_volume: float = 1.0,
speed: float = 1.0,
player_selector: str = "@a",
) -> Tuple[List[SingleCommand], int]:
"""
使用金羿的转换思路使用完全填充算法优化音感后将midi转换为我的世界命令列表并输出每个音符之后的延迟
Parameters
----------
max_volume: float
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
player_selector: str
玩家选择器,默认为`@a`
Returns
-------
tuple( list[SingleCommand,...], int音乐时长游戏刻 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
self.to_music_channels()
note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[])
InstID = -1
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# nowChannel = []
for track_no, track in self.channels[i].items():
noteMsgs = []
MsgIndex = []
for msg in track:
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],
track_number=track_no,
)
)
noteMsgs.pop(MsgIndex.index(msg[1]))
MsgIndex.pop(MsgIndex.index(msg[1]))
del InstID
tracks = {}
InstID = -1
# open("RES.TXT", "w", encoding="utf-8").write(str(note_channels))
# 此处 我们把通道视为音轨
for no, track in note_channels.items():
# 如果当前通道为空 则跳过
if not track:
continue
SpecialBits = True if no == 9 else False
for note in track:
for every_note in self._linear_note(
note, 50 if note.track_no == 0 else 500
):
soundID, _X = (
self.perc_inst_to_soundID_withX(note.pitch)
if SpecialBits
else self.inst_to_souldID_withX(note.inst)
)
score_now = round(every_note[0] / speed / 50)
try:
tracks[score_now].append(
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} "
+ (
""
if SpecialBits
else f"{2 ** ((note.pitch - 60 - _X) / 12)}"
)
)
except KeyError:
tracks[score_now] = [
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} "
+ (
""
if SpecialBits
else f"{2 ** ((note.pitch - 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(
SingleCommand(
tracks[all_ticks[i]][j],
tick_delay=(
0
if j != 0
else (
all_ticks[i] - all_ticks[i - 1]
if i != 0
else all_ticks[i]
)
),
annotation="{}播放{}%{}".format(
mctick2timestr(i), max_volume * 100, ""
),
)
)
self.music_command_list = results
self.music_tick_num = max(all_ticks)
return results, self.music_tick_num
class FutureMidiConvertM5(MidiConvert):
"""
加入同刻偏移算法优化音感
"""
# 神奇的偏移音
def to_command_list_in_delay(
self,
max_volume: float = 1.0,
speed: float = 1.0,
player_selector: str = "@a",
) -> Tuple[List[SingleCommand], int]:
"""
使用金羿的转换思路使用同刻偏移算法优化音感后将midi转换为我的世界命令列表并输出每个音符之后的延迟
Parameters
----------
max_volume: float
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
player_selector: str
玩家选择器,默认为`@a`
Returns
-------
tuple( list[SingleCommand,...], int音乐时长游戏刻 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
self.to_music_channels()
tracks = {}
InstID = -1
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
# nowChannel = []
for track_no, track in self.channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
self.perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else self.inst_to_souldID_withX(InstID)
)
score_now = round(msg[-1] / float(speed) / 50)
# print(score_now)
try:
tracks[score_now].append(
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
+ (
""
if SpecialBits
else f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
)
except KeyError:
tracks[score_now] = [
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
+ (
""
if SpecialBits
else 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(
SingleCommand(
tracks[all_ticks[i]][j],
tick_delay=(
(
0
if (
(all_ticks[i + 1] - all_ticks[i])
/ len(tracks[all_ticks[i]])
< 1
)
else 1
)
if j != 0
else (
(
all_ticks[i]
- all_ticks[i - 1]
- (
0
if (
(all_ticks[i] - all_ticks[i - 1])
/ len(tracks[all_ticks[i - 1]])
< 1
)
else (len(tracks[all_ticks[i - 1]]) - 1)
)
)
if i != 0
else all_ticks[i]
)
),
annotation="{}播放{}%{}".format(
mctick2timestr(
i + 0
if (
(all_ticks[i + 1] - all_ticks[i])
/ len(tracks[all_ticks[i]])
< 1
)
else j
),
max_volume * 100,
"",
),
)
)
self.music_command_list = results
self.music_tick_num = max(all_ticks)
return results, self.music_tick_num
class FutureMidiConvertM6(MidiConvert):
"""
加入插值算法优化音感,但仅用于第一音轨
"""
# TODO 没写完的!!!!

View File

@@ -31,69 +31,6 @@ Terms & Conditions: ../License.md
def _toCmdList_m1(
self,
scoreboardname: str = "mscplay",
volume: float = 1.0,
speed: float = 1.0) -> list:
"""
使用Dislink Sforza的转换思路将midi转换为我的世界命令列表
:param scoreboardname: 我的世界的计分板名称
:param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:return: tuple(命令列表, 命令个数, 计分板最大值)
"""
tracks = []
if volume > 1:
volume = 1
if volume <= 0:
volume = 0.001
commands = 0
maxscore = 0
for i, track in enumerate(self.midi.tracks):
ticks = 0
instrumentID = 0
singleTrack = []
for msg in track:
ticks += msg.time
# print(msg)
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
else:
if msg.type == "program_change":
# print("TT")
instrumentID = msg.program
if msg.type == "note_on" and msg.velocity != 0:
nowscore = round(
(ticks * tempo)
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
)
maxscore = max(maxscore, nowscore)
soundID, _X = self.__Inst2soundID_withX(instrumentID)
singleTrack.append(
"execute @a[scores={" +
str(scoreboardname) +
"=" +
str(nowscore) +
"}" +
f"] ~ ~ ~ playsound {soundID} @s ~ ~{1 / volume - 1} ~ {msg.velocity * (0.7 if msg.channel == 0 else 0.9)} {2 ** ((msg.note - 60 - _X) / 12)}")
commands += 1
if len(singleTrack) != 0:
tracks.append(singleTrack)
return [tracks, commands, maxscore]
# ============================ # ============================
@@ -209,16 +146,13 @@ if __name__ == '__main__':
# ============================ # ============================
from typing import Literal
from ..constants import x,y,z
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
# 不要用 没写完 # 不要用 没写完
def delay_to_note_blocks( def delay_to_note_blocks(
baseblock: str = "stone", baseblock: str = "stone",
position_forward: Union(x, y, z) = z, position_forward: Literal['x','y','z'] = z,
max_height: int = 64,
): ):
"""传入音符,生成以音符盒存储的红石音乐 """传入音符,生成以音符盒存储的红石音乐
:param: :param:
@@ -229,10 +163,6 @@ def delay_to_note_blocks(
from TrimMCStruct import Structure, Block from TrimMCStruct import Structure, Block
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
len(commands), max_height
)
struct = Structure( struct = Structure(
(_sideLength, max_height, _sideLength), # 声明结构大小 (_sideLength, max_height, _sideLength), # 声明结构大小
) )
@@ -243,7 +173,6 @@ def delay_to_note_blocks(
# 1拍 x 2.5 rt # 1拍 x 2.5 rt
def placeNoteBlock():
for i in notes: for i in notes:
error = True error = True
try: try:
@@ -294,12 +223,3 @@ def delay_to_note_blocks(
startpos[0] += posadder[0] startpos[0] += posadder[0]
startpos[1] += posadder[1] startpos[1] += posadder[1]
startpos[2] += posadder[2] startpos[2] += posadder[2]
# e = True
try:
placeNoteBlock()
# e = False
except: # ValueError
log("无法放置方块了,可能是因为区块未加载叭")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
"""
存放非音·创本体的附加内容(插件?)
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"ConvertConfig",
]
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"))
from .main import *

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
"""
存放关于文件打包的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import os
import uuid
import zipfile
import datetime
from typing import List, Union, Literal
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()
def behavior_mcpack_manifest(
pack_description: str = "",
pack_version: Union[List[int], Literal[None]] = None,
pack_name: str = "",
pack_uuid: Union[str, Literal[None]] = None,
modules_description: str = "",
modules_version: List[int] = [0, 0, 1],
modules_uuid: Union[str, Literal[None]] = None,
):
"""
生成一个我的世界行为包组件的定义清单文件
"""
if not pack_version:
now_date = datetime.datetime.now()
pack_version = [
now_date.year,
now_date.month * 100 + now_date.day,
now_date.hour * 100 + now_date.minute,
]
return {
"format_version": 1,
"header": {
"description": pack_description,
"version": pack_version,
"name": pack_name,
"uuid": str(uuid.uuid4()) if not pack_uuid else pack_uuid,
},
"modules": [
{
"description": modules_description,
"type": "data",
"version": modules_version,
"uuid": str(uuid.uuid4()) if not modules_uuid else modules_uuid,
}
],
}

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

@@ -0,0 +1,215 @@
# -*- coding: utf-8 -*-
"""
存放有关BDX结构操作的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import List
from ..constants import x, y, z
from ..subclass import SingleCommand
from .common import bottem_side_length_of_smallest_square_bottom_box
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: List[SingleCommand],
max_height: int = 64,
):
"""
:param commands: 指令列表(指令, 延迟)
:param max_height: 生成结构最大高度
:return 成功与否,成功返回(True,未经过压缩的源,结构占用大小),失败返回(False,str失败原因)
"""
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
len(commands_list), max_height
)
_bytes = b""
y_forward = True
z_forward = True
now_y = 0
now_z = 0
now_x = 0
for command in commands_list:
_bytes += form_command_block_in_BDX_bytes(
command.command_text,
(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=command.conditional,
needRedstone=False,
tickDelay=command.delay,
customName=command.annotation_text,
executeOnFirstTick=False,
trackOutput=True,
)
# (1 if y_forward else 0) if ( # 如果y+则向上,反之向下
# ((now_y != 0) and (not y_forward)) # 如果不是y轴上首个方块
# or (y_forward and (now_y != (max_height - 1))) # 如果不是y轴上末端方块
# ) else ( # 否则即是y轴末端或首个方块
# (3 if z_forward else 2) if ( # 如果z+则向z轴正方向反之负方向
# ((now_z != 0) and (not z_forward)) # 如果不是z轴上的首个方块
# or (z_forward and (now_z != _sideLength - 1)) # 如果不是z轴上的末端方块
# ) else 5 # 否则则要面向x轴正方向
# )
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,24 @@
# -*- coding: utf-8 -*-
"""
用以生成BDX结构文件的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_BDX_file_in_score",
"to_BDX_file_in_delay"
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -0,0 +1,220 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import os
import brotli
from ...main import MidiConvert
from ...subclass import SingleCommand
from ..bdx import (bdx_move, commands_to_BDX_bytes,
form_command_block_in_BDX_bytes, x, y, z)
from ..main import ConvertConfig
def to_BDX_file_in_score(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
author: str = "Eilles",
max_height: int = 64,
):
"""
将midi以计分播放器形式转换为BDX结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
是否自动重置计分板
author: str
作者名称
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟, tuple[int,]结构大小, tuple[int,]终点坐标]
"""
cmdlist, command_count, max_score = midi_cvt.to_command_list_in_score(
scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx")
),
"w+",
) as f:
f.write("BD@")
_bytes = (
b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00"
)
cmdBytes, size, finalPos = commands_to_BDX_bytes(
midi_cvt.music_command_list
+ (
[
SingleCommand(
command="scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(max_score + 20)
+ "}] "
+ scoreboard_name,
annotation="自动重置计分板",
)
]
if auto_reset
else []
),
max_height - 1,
)
if data_cfg.progressbar_style:
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
midi_cvt.form_progress_bar(
max_score, scoreboard_name, data_cfg.progressbar_style
),
max_height - 1,
)
_bytes += pgbBytes
_bytes += bdx_move(y, -pgbNowPos[1])
_bytes += bdx_move(z, -pgbNowPos[2])
_bytes += bdx_move(x, 2)
size[0] += 2 + pgbSize[0]
size[1] = max(size[1], pgbSize[1])
size[2] = max(size[2], pgbSize[2])
_bytes += cmdBytes
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx")
),
"ab+",
) as f:
f.write(brotli.compress(_bytes + b"XE"))
return command_count, max_score, size, finalPos
def to_BDX_file_in_delay(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
author: str = "Eilles",
max_height: int = 64,
):
"""
使用method指定的转换算法将midi转换为BDX结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
player: str
玩家选择器,默认为`@a`
author: str
作者名称
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟, tuple[int,]结构大小, tuple[int,]终点坐标]
"""
cmdlist, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx")
),
"w+",
) as f:
f.write("BD@")
_bytes = (
b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00"
)
cmdBytes, size, finalPos = commands_to_BDX_bytes(cmdlist, max_height - 1)
if data_cfg.progressbar_style:
scb_name = midi_cvt.midi_music_name[:3] + "Pgb"
_bytes += form_command_block_in_BDX_bytes(
r"scoreboard objectives add {} dummy {}".replace(r"{}", scb_name),
1,
customName="初始化进度条",
)
_bytes += bdx_move(z, 2)
_bytes += form_command_block_in_BDX_bytes(
r"scoreboard players add {} {} 1".format(player, scb_name),
1,
1,
customName="显示进度条并加分",
)
_bytes += bdx_move(y, 1)
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style),
max_height - 1,
)
_bytes += pgbBytes
_bytes += bdx_move(y, -1 - pgbNowPos[1])
_bytes += bdx_move(z, -2 - pgbNowPos[2])
_bytes += bdx_move(x, 2)
_bytes += form_command_block_in_BDX_bytes(
r"scoreboard players reset {} {}".format(player, scb_name),
1,
customName="置零进度条",
)
_bytes += bdx_move(y, 1)
size[0] += 2 + pgbSize[0]
size[1] = max(size[1], pgbSize[1])
size[2] = max(size[2], pgbSize[2])
size[1] += 1
_bytes += cmdBytes
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx")
),
"ab+",
) as f:
f.write(brotli.compress(_bytes + b"XE"))
return len(cmdlist), max_delay, size, finalPos

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
"""
存放通用的普遍性的插件内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
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,23 @@
# -*- coding: utf-8 -*-
"""
用以生成函数附加包的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_function_addon_in_score"
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import json
import os
import shutil
from typing import Tuple
from ...main import MidiConvert
from ..archive import behavior_mcpack_manifest, compress_zipfile
from ..main import ConvertConfig
def to_function_addon_in_score(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
) -> Tuple[int, int]:
"""
将midi以计分播放器形式转换为我的世界函数附加包
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
是否自动重置计分板
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_score(
scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier
)
# 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目然后创建
if os.path.exists(f"{data_cfg.dist_path}/temp/functions/"):
shutil.rmtree(f"{data_cfg.dist_path}/temp/functions/")
os.makedirs(f"{data_cfg.dist_path}/temp/functions/mscplay")
# 写入manifest.json
with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包MCFUNCTION(MCPACK) 计分播放器 - 由 音·创 生成",
pack_name=midi_cvt.midi_music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
),
fp=f,
indent=4,
)
# 将命令列表写入文件
index_file = open(
f"{data_cfg.dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
for i in range(len(cmdlist)):
index_file.write(f"function mscplay/track{i + 1}\n")
with open(
f"{data_cfg.dist_path}/temp/functions/mscplay/track{i + 1}.mcfunction",
"w",
encoding="utf-8",
) as f:
f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]]))
index_file.writelines(
(
"scoreboard players add @a[scores={"
+ scoreboard_name
+ "=1..}] "
+ scoreboard_name
+ " 1\n",
(
"scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(maxscore + 20)
+ "..}]"
+ f" {scoreboard_name}\n"
)
if auto_reset
else "",
f"function mscplay/progressShow\n" if data_cfg.progressbar_style else "",
)
)
if data_cfg.progressbar_style:
with open(
f"{data_cfg.dist_path}/temp/functions/mscplay/progressShow.mcfunction",
"w",
encoding="utf-8",
) as f:
f.writelines(
"\n".join(
[
single_cmd.cmd
for single_cmd in midi_cvt.form_progress_bar(
maxscore, scoreboard_name, data_cfg.progressbar_style
)
]
)
)
index_file.close()
if os.path.exists(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack"):
os.remove(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack")
compress_zipfile(
f"{data_cfg.dist_path}/temp/",
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
)
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
return maxlen, maxscore

View File

@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
"""
存放附加内容功能
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from dataclasses import dataclass
from typing import Tuple, Union, Literal
from ..constants import DEFAULT_PROGRESSBAR_STYLE
@dataclass(init=False)
class ConvertConfig:
"""
转换通用设置存储类
"""
volume_ratio: float
"""音量比例"""
speed_multiplier: float
"""速度倍率"""
progressbar_style: Union[Tuple[str, Tuple[str, str]], Literal[None]]
"""进度条样式组"""
dist_path: str
"""输出目录"""
def __init__(
self,
output_path: str,
volume: float = 1.0,
speed: float = 1.0,
progressbar: Union[bool, Tuple[str, Tuple[str, str]]] = True,
):
"""
将已经转换好的数据内容指令载入MC可读格式
Parameters
----------
output_path: str
生成内容的输出目录
volume: float
音量比率,范围为(0,1],其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度倍率,注意:这里的速度指的是播放速度倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
progressbar: bool|tuple[str, Tuple[str,]]
进度条,当此参数为 `True` 时使用默认进度条,为其他的**值为真**的参数时识别为进度条自定义参数,为其他**值为假**的时候不生成进度条
"""
self.dist_path = output_path
"""输出目录"""
self.volume_ratio = volume
"""音量比例"""
self.speed_multiplier = speed
"""速度倍率"""
if progressbar:
# 此处是对于仅有 True 的参数和自定义参数的判断
# 改这一段没🐎
if progressbar is True:
self.progressbar_style = DEFAULT_PROGRESSBAR_STYLE
"""进度条样式组"""
else:
self.progressbar_style = progressbar
"""进度条样式组"""
else:
self.progressbar_style = None
"""进度条样式组"""

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
"""
用以生成单个mcstructure文件的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_mcstructure_file_in_delay",
"to_mcstructure_file_in_redstone_CD",
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import os
from typing import Literal
from ...exceptions import CommandFormatError
from ...main import MidiConvert
from ..main import ConvertConfig
from ..mcstructure import commands_to_structure,commands_to_redstone_delay_structure
def to_mcstructure_file_in_delay(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
max_height: int = 64,
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
player: str
玩家选择器,默认为`@a`
max_height: int
生成结构最大高度
Returns
-------
tuple[tuple[int,]结构大小, int音乐总延迟]
"""
if midi_cvt.enable_old_exe_format:
raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。")
cmd_list, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
struct, size, end_pos = commands_to_structure(cmd_list, max_height - 1)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.mcstructure")
),
"wb+",
) as f:
struct.dump(f)
return size, max_delay
def to_mcstructure_file_in_redstone_CD(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
axis_side: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"] = "z+",
basement_block: str = "concrete",
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
player: str
玩家选择器,默认为`@a`
axis_side: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"]
生成结构的延展方向
basement_block: str
结构的基底方块
Returns
-------
tuple[tuple[int,]结构大小, int音乐总延迟]
"""
if midi_cvt.enable_old_exe_format:
raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。")
cmd_list, max_delay, max_multiple_cmd = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
struct, size, end_pos = commands_to_redstone_delay_structure(cmd_list,max_delay,max_multiple_cmd, basement_block, axis_side)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.mcstructure")
),
"wb+",
) as f:
struct.dump(f)
return size, max_delay

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
"""
用以生成结构附加包的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_mcstructure_addon_in_delay"
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import json
import os
import shutil
from TrimMCStruct import Structure
from ...exceptions import CommandFormatError
from ...main import MidiConvert
from ..archive import behavior_mcpack_manifest, compress_zipfile
from ..main import ConvertConfig
from ..mcstructure import commands_to_structure, form_command_block_in_NBT_struct
def to_mcstructure_addon_in_delay(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
max_height: int = 64,
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件后打包成附加包并在附加包中生成相应地导入函数
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
player: str
玩家选择器,默认为`@a`
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
if midi_cvt.enable_old_exe_format:
raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。")
command_list, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
# 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建
if os.path.exists(f"{data_cfg.dist_path}/temp/"):
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
os.makedirs(f"{data_cfg.dist_path}/temp/functions/")
os.makedirs(f"{data_cfg.dist_path}/temp/structures/")
# 写入manifest.json
with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包MCSTRUCTURE(MCPACK) 延迟播放器 - 由 音·创 生成",
pack_name=midi_cvt.midi_music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
),
fp=f,
indent=4,
)
# 将命令列表写入文件
index_file = open(
f"{data_cfg.dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
struct, size, end_pos = commands_to_structure(command_list, max_height - 1)
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_main.mcstructure",
)
),
"wb+",
) as f:
struct.dump(f)
del struct
if data_cfg.progressbar_style:
scb_name = midi_cvt.midi_music_name[:3] + "Pgb"
index_file.write("scoreboard objectives add {0} dummy {0}\n".format(scb_name))
struct_a = Structure(
(1, 1, 1),
)
struct_a.set_block(
(0, 0, 0),
form_command_block_in_NBT_struct(
r"scoreboard players add {} {} 1".format(player, scb_name),
(0, 0, 0),
1,
1,
alwaysRun=False,
customName="显示进度条并加分",
),
)
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_start.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
index_file.write(f"structure load {midi_cvt.midi_music_name}_start ~ ~ ~1\n")
pgb_struct, pgbSize, pgbNowPos = commands_to_structure(
midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style),
max_height - 1,
)
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_pgb.mcstructure",
)
),
"wb+",
) as f:
pgb_struct.dump(f)
index_file.write(f"structure load {midi_cvt.midi_music_name}_pgb ~ ~1 ~1\n")
struct_a = Structure(
(1, 1, 1),
)
struct_a.set_block(
(0, 0, 0),
form_command_block_in_NBT_struct(
r"scoreboard players reset {} {}".format(player, scb_name),
(0, 0, 0),
1,
0,
alwaysRun=False,
customName="重置进度条计分板",
),
)
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_reset.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
del struct_a, pgb_struct
index_file.write(
f"structure load {midi_cvt.midi_music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
)
index_file.write(
f"structure load {midi_cvt.midi_music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
)
else:
index_file.write(f"structure load {midi_cvt.midi_music_name}_main ~ ~ ~1\n")
index_file.close()
if os.path.exists(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack"):
os.remove(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack")
compress_zipfile(
f"{data_cfg.dist_path}/temp/",
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
)
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
return len(command_list), max_delay

View File

@@ -0,0 +1,391 @@
# -*- coding: utf-8 -*-
"""
存放有关MCSTRUCTURE结构操作的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import List, Literal, Tuple
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
from ..constants import x, y, z
from ..subclass import SingleCommand
from .common import bottem_side_length_of_smallest_square_bottom_box
import struct
def antiaxis(axis: Literal['x','z','X','Z']):
return z if axis == x else x
def forward_IER(forward: bool):
return 1 if forward else -1
AXIS_PARTICULAR_VALUE = {
x: {
True: 5,
False: 4,
},
y: {
True: 1,
False: 0,
},
z: {
True: 3,
False: 2,
},
}
def command_statevalue(axis_: Literal['x','y','z','X','Y','Z'],forward_:bool):
return AXIS_PARTICULAR_VALUE[axis_.lower()][forward_]
def form_note_block_in_NBT_struct(
note: int,
coordinate: Tuple[int, int, int],
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],
} # type: ignore
},
)
def form_repeater_in_NBT_struct(delay: int, facing: int):
"""生成中继器方块
:param facing: 朝向:
Z- 北 0
X- 东 1
Z+ 南 2
X+ 西 3
:param delay: 0~3
: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],
} # type: ignore
},
compability_version=17959425,
)
def commands_to_structure(
commands: List[SingleCommand],
max_height: int = 64,
):
"""
:param commands: 指令列表
:param max_height: 生成结构最大高度
:return 结构类,结构占用大小,终点坐标
"""
_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 command in commands:
coordinate = (now_x, now_y, now_z)
struct.set_block(
coordinate,
form_command_block_in_NBT_struct(
command=command.command_text,
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=command.delay,
customName=command.annotation_text,
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),
)
def commands_to_redstone_delay_structure(
commands: List[SingleCommand],
delay_length: int,
max_multicmd_length: int,
base_block: str = "concrete",
axis_: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"] = "z+",
) -> Tuple[Structure, Tuple[int, int, int], Tuple[int, int, int]]:
"""
:param commands: 指令列表
:param delay_length: 延时总长
:param max_multicmd_length: 最大同时播放的音符数量
:param base_block: 生成结构的基底方块
:param axis_: 生成结构的延展方向
:return 结构类,结构占用大小,终点坐标
"""
if axis_ in ["z+", "Z+"]:
extensioon_direction = z
aside_direction = x
repeater_facing = 2
forward = True
elif axis_ in ["z-", "Z-"]:
extensioon_direction = z
aside_direction = x
repeater_facing = 0
forward = False
elif axis_ in ["x+", "X+"]:
extensioon_direction = x
aside_direction = z
repeater_facing = 3
forward = True
elif axis_ in ["x-", "X-"]:
extensioon_direction = x
aside_direction = z
repeater_facing = 1
forward = False
else:
raise ValueError(f"axis_({axis_}) 参数错误。")
goahead = forward_IER(forward)
struct = Structure(
(round(delay_length/2+0.5+len(commands)) if extensioon_direction == x else max_multicmd_length, 3, round(delay_length/2+0.5+len(commands)) if extensioon_direction == z else max_multicmd_length)
)
pos_now = {x:(0 if forward else struct.size[0]),y:0,z:(0 if forward else struct.size[2])}
first_impluse = True
for cmd in commands:
single_repeater_value = round(cmd.delay / 2) % 4 - 1
additional_repeater = round(cmd.delay / 2)// 4
for i in range(additional_repeater):
struct.set_block(
tuple(pos_now.values()),
Block("minecraft", base_block,),
)
struct.set_block(
(pos_now[x],1,pos_now[z]),
form_repeater_in_NBT_struct(
delay=3,
facing=repeater_facing,
),
)
pos_now[extensioon_direction] += goahead
first_impluse = True
if single_repeater_value >= 0:
struct.set_block(
tuple(pos_now.values()),
Block("minecraft", base_block,),
)
struct.set_block(
(pos_now[x],1,pos_now[z]),
form_repeater_in_NBT_struct(
delay=single_repeater_value,
facing=repeater_facing,
),
)
pos_now[extensioon_direction] += goahead
first_impluse = True
struct.set_block(
(pos_now[x],1,pos_now[z]),
form_command_block_in_NBT_struct(
command=cmd.command_text,
coordinate=(pos_now[x],1,pos_now[z]),
particularValue=command_statevalue(extensioon_direction, forward),
# impluse= (0 if first_impluse else 2),
impluse=0,
condition=False,
alwaysRun=False,
tickDelay=0,
customName=cmd.annotation_text,
),
)
struct.set_block(
(pos_now[x],2,pos_now[z]),
Block("minecraft", "redstone_wire"),
)
pos_now[extensioon_direction] += goahead
first_impluse = False
return struct, struct.size, tuple(pos_now.values())

View File

@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
"""
存放有关红石音乐生成操作的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from ..exceptions import NotDefineProgramError, ZeroSpeedError
from ..main import MidiConvert
from ..subclass import SingleCommand
# 你以为写完了吗?其实并没有
def to_note_list(
midi_cvt: MidiConvert,
speed: float = 1.0,
) -> list:
"""
使用金羿的转换思路将midi转换为我的世界音符盒所用的音高列表并输出每个音符之后的延迟
Parameters
----------
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
Returns
-------
tuple( list[tuple(str指令, int距离上一个指令的延迟 ),...], int音乐时长游戏刻 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
midi_channels = (
midi_cvt.to_music_channels() if not midi_cvt.channels else midi_cvt.channels
)
tracks = {}
# 此处 我们把通道视为音轨
for i in midi_channels.keys():
# 如果当前通道为空 则跳过
if not midi_channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
# nowChannel = []
for track_no, track in midi_channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
try:
soundID, _X = (
midi_cvt.perc_inst_to_soundID_withX(InstID)
if SpecialBits
else midi_cvt.inst_to_souldID_withX(InstID)
)
except UnboundLocalError as E:
soundID, _X = (
midi_cvt.perc_inst_to_soundID_withX(-1)
if SpecialBits
else midi_cvt.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

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
"""
存放有关Schematic结构生成的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import nbtschematic

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
"""
用以生成Schematic结构的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from ..schematic import *

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
"""
存放有关WebSocket服务器操作的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import fcwslib
# 这个库有问题,正在检修
class Plugin(fcwslib.Plugin):
async def on_connect(self) -> None:
print('对象已被连接')
await self.send_command('list', callback=self.list)
await self.subscribe('PlayerMessage', callback=self.player_message)
async def on_disconnect(self) -> None:
print('对象停止连接')
async def on_receive(self, response) -> None:
print('已接收非常规回复 {}'.format(response))
async def list(self, response) -> None:
print('已收取指令执行回复 {}'.format(response))
async def player_message(self, response) -> None:
print('已收取玩家事件回复 {}'.format(response))

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
"""
用以启动WebSocket服务器播放的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = []
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import fcwslib
from ...main import MidiConvert
from ..main import ConvertConfig
from ...subclass import SingleCommand
def open_websocket_server(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
server_dist: str = "localhost",
server_port: int = 8000,
):
wssever = fcwslib.Server(server=server_dist,port=server_port,debug_mode=False)

383
Musicreater/previous.py Normal file
View File

@@ -0,0 +1,383 @@
# -*- coding: utf-8 -*-
"""
旧版本转换功能以及已经弃用的函数
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import Dict, List, Tuple, Union
from .exceptions import *
from .main import MidiConvert, mido
from .subclass import *
from .utils import *
class ObsoleteMidiConvert(MidiConvert):
"""
我说一句话:
这些破烂老代码能跑得起来就是谢天谢地,你们还指望我怎么样?这玩意真的不会再维护了,我发誓!
"""
def to_command_list_method1(
self,
scoreboard_name: str = "mscplay",
MaxVolume: float = 1.0,
speed: float = 1.0,
) -> list:
"""
使用Dislink Sforza的转换思路将midi转换为我的世界命令列表
:param scoreboard_name: 我的世界的计分板名称
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:return: tuple(命令列表, 命令个数, 计分板最大值)
"""
# :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
tracks = []
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
if not self.midi:
raise MidiUnboundError(
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象这是不可复用的。"
)
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
commands = 0
maxscore = 0
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
# 分轨的思路其实并不好,但这个算法就是这样
# 所以我建议用第二个方法 _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:
nowscore = round(
(ticks * tempo)
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
)
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_m1(
self,
scoreboardname: str = "mscplay",
volume: float = 1.0,
speed: float = 1.0) -> list:
"""
使用Dislink Sforza的转换思路将midi转换为我的世界命令列表
:param scoreboardname: 我的世界的计分板名称
:param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:return: tuple(命令列表, 命令个数, 计分板最大值)
"""
tracks = []
if volume > 1:
volume = 1
if volume <= 0:
volume = 0.001
commands = 0
maxscore = 0
for i, track in enumerate(self.midi.tracks):
ticks = 0
instrumentID = 0
singleTrack = []
for msg in track:
ticks += msg.time
# print(msg)
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
else:
if msg.type == "program_change":
# print("TT")
instrumentID = msg.program
if msg.type == "note_on" and msg.velocity != 0:
nowscore = round(
(ticks * tempo)
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
)
maxscore = max(maxscore, nowscore)
soundID, _X = self.__Inst2soundID_withX(instrumentID)
singleTrack.append(
"execute @a[scores={" +
str(scoreboardname) +
"=" +
str(nowscore) +
"}" +
f"] ~ ~ ~ playsound {soundID} @s ~ ~{1 / volume - 1} ~ {msg.velocity * (0.7 if msg.channel == 0 else 0.9)} {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,
) -> tuple:
"""
使用神羽和金羿的转换思路将midi转换为我的世界命令列表
:param scoreboard_name: 我的世界的计分板名称
:param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:return: tuple(命令列表, 命令个数, 计分板最大值)
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
tracks = []
cmdAmount = 0
maxScore = 0
InstID = -1
self.to_music_channels()
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
if i == 9:
SpecialBits = True
else:
SpecialBits = False
nowTrack = []
for track_no, track in self.channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
self.perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else self.inst_to_souldID_withX(InstID)
)
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:
raise ZeroSpeedError("播放速度仅可为正实数")
if not self.midi:
raise MidiUnboundError(
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象这是不可复用的。"
)
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
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:
raise ZeroSpeedError("播放速度仅可为正实数")
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
InstID = -1
self.to_music_channels()
results = []
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
if i == 9:
SpecialBits = True
else:
SpecialBits = False
for track_no, track in self.channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
self.perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else self.inst_to_souldID_withX(InstID)
)
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)]

279
Musicreater/subclass.py Normal file
View File

@@ -0,0 +1,279 @@
# -*- coding: utf-8 -*-
"""
存储许多非主要的相关类
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from dataclasses import dataclass
from typing import Optional
from .constants import PERCUSSION_INSTRUMENT_LIST
@dataclass(init=False)
class SingleNote:
"""存储单个音符的类"""
instrument: int
"""乐器编号"""
note: int
"""音符编号"""
velocity: int
"""力度/响度"""
start_time: int
"""开始之时 ms"""
duration: int
"""音符持续时间 ms"""
track_no: int
"""音符所处的音轨"""
def __init__(
self,
instrument: int,
pitch: int,
velocity: int,
startTime: int,
lastTime: int,
track_number: int = 0,
):
"""用于存储单个音符的类
:param instrument 乐器编号
:param pitch 音符编号
:param velocity 力度/响度
:param startTime 开始之时(ms)
注:此处的时间是用从乐曲开始到当前的毫秒数
:param lastTime 音符延续时间(ms)"""
self.instrument: int = instrument
"""乐器编号"""
self.note: int = pitch
"""音符编号"""
self.velocity: int = velocity
"""力度/响度"""
self.start_time: int = startTime
"""开始之时 ms"""
self.duration: int = lastTime
"""音符持续时间 ms"""
self.track_no: int = track_number
"""音符所处的音轨"""
@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.start_time}, lastTime = {self.duration}, )"
)
def __tuple__(self):
return self.inst, self.note, self.velocity, self.start_time, self.duration
def __dict__(self):
return {
"inst": self.inst,
"pitch": self.note,
"velocity": self.velocity,
"startTime": self.start_time,
"lastTime": self.duration,
}
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()
@dataclass(init=False)
class SingleCommand:
"""存储单个指令的类"""
command_text: str
"""指令文本"""
conditional: bool
"""执行是否有条件"""
delay: int
"""执行的延迟"""
annotation_text: str
"""指令注释"""
def __init__(
self,
command: str,
condition: bool = False,
tick_delay: int = 0,
annotation: str = "",
):
"""
存储单个指令的类
Parameters
----------
command: str
指令
condition: bool
是否有条件
tick_delay: int
执行延时
annotation: str
注释
"""
self.command_text = command
self.conditional = condition
self.delay = tick_delay
self.annotation_text = annotation
def copy(self):
return SingleCommand(
command=self.command_text,
condition=self.conditional,
tick_delay=self.delay,
annotation=self.annotation_text,
)
@property
def cmd(self) -> str:
"""
我的世界函数字符串(包含注释)
"""
return self.__str__()
def __str__(self) -> str:
"""
转为我的世界函数文件格式(包含注释)
"""
return "#[{cdt}]<{delay}> {ant}\n{cmd}".format(
cdt="CDT" if self.conditional else "",
delay=self.delay,
ant=self.annotation_text,
cmd=self.command_text,
)
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()
@dataclass(init=False)
class SingleNoteBox:
"""存储单个音符盒"""
instrument_block: str
"""乐器方块"""
note_value: int
"""音符盒音高"""
annotation_text: str
"""音符注释"""
is_percussion: bool
"""是否为打击乐器"""
def __init__(
self,
instrument_block_: str,
note_value_: int,
percussion: Optional[bool] = None,
annotation: str = "",
):
"""用于存储单个音符盒的类
:param instrument_block_ 音符盒演奏所使用的乐器方块
:param note_value_ 音符盒的演奏音高
:param percussion 此音符盒乐器是否作为打击乐处理
注:若为空,则自动识别是否为打击乐器
:param annotation 音符注释"""
self.instrument_block = instrument_block_
"""乐器方块"""
self.note_value = note_value_
"""音符盒音高"""
self.annotation_text = annotation
"""音符注释"""
if percussion is None:
self.is_percussion = percussion in PERCUSSION_INSTRUMENT_LIST
else:
self.is_percussion = percussion
@property
def inst(self) -> str:
"""获取音符盒下的乐器方块"""
return self.instrument_block
@inst.setter
def inst(self, inst_):
self.instrument_block = inst_
@property
def note(self) -> int:
"""获取音符盒音调特殊值"""
return self.note_value
@note.setter
def note(self, note_):
self.note_value = note_
@property
def annotation(self) -> str:
"""获取音符盒的备注"""
return self.annotation_text
@annotation.setter
def annotation(self, annotation_):
self.annotation_text = annotation_
def copy(self):
return SingleNoteBox(
instrument_block_=self.instrument_block,
note_value_=self.note_value,
annotation=self.annotation_text,
)
def __str__(self) -> str:
return f"Note(inst = {self.inst}, note = {self.note}, )"
def __tuple__(self) -> tuple:
return self.inst, self.note, self.annotation
def __dict__(self) -> dict:
return {
"inst": self.inst,
"note": self.note,
"annotation": self.annotation,
}
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()

View File

@@ -1,459 +1,39 @@
import math # -*- coding: utf-8 -*-
import os """
存放主程序所必须的功能性内容
"""
bdx_key = { """
"x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"], 版权所有 © 2023 音·创 开发者
"y": [b"\x11", b"\x10", b"\x1d", b"\x16", b"\x17"], Copyright © 2023 all the developers of Musicreater
"z": [b"\x13", b"\x12", b"\x1e", b"\x18", b"\x19"],
}
"""key存储了方块移动指令的数据其中可以用key[x|y|z][0|1]来表示xyz的减或增
而key[][2+]是用来增加指定数目的"""
x = "x" 开源相关声明请见 仓库根目录下的 License.md
y = "y" Terms & Conditions: License.md in the root directory
z = "z" """
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import Any, Dict
def bdx_move(axis: str, value: int): def mctick2timestr(mc_tick: int) -> str:
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
""" """
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,
):
""" """
使用指定项目返回指定的指令方块放置指令项 return str(int(int(mc_tick / 20) / 60)) + ":" + str(int(int(mc_tick / 20) % 60))
: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 def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]:
""" """
block = b"\x24" + particularValue.to_bytes(2, byteorder="big", signed=False) 空MIDI通道字典
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,
):
"""
:param commands: 指令列表(指令, 延迟)
:param max_height: 生成结构最大高度
:return 成功与否,成功返回(True,未经过压缩的源,结构占用大小),失败返回(False,str失败原因)
""" """
_sideLength = bottem_side_length_of_smallest_square_bottom_box( return dict(
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, i,
max_height if now_x or now_z else now_y, (staff.copy() if isinstance(staff, (dict, list)) else staff),
_sideLength if now_x else now_z, ) # 这告诉我们你不能忽略任何一个复制的序列因为它真的我哭死折磨我一整天全在这个bug上了
), for i in range(channel_count)
(now_x, now_y, now_z),
) )

View File

@@ -1,4 +1,3 @@
<h1 align="center"> <h1 align="center">
· Musicreater · Musicreater
</h1> </h1>
@@ -8,7 +7,7 @@
</img> </img>
</p> </p>
<h3 align="center">一款免费开源的 我的世界 MIDI音乐转换库</h3> <h3 align="center">一款免费开源的我的世界数字音频转换库</h3>
<p align="center"> <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">
@@ -18,10 +17,6 @@
</a> </a>
<p> <p>
[![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/) [![][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) [![CodeStyle: black]](https://github.com/psf/black)
@@ -34,19 +29,18 @@
[![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 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) [![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) 欢迎加群[861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
## 下载安装 ## 安装 🔳
- 使用 pypi
- 使用pypi
```bash ```bash
pip install Musicreater pip install Musicreater
``` ```
@@ -55,7 +49,9 @@
```bash ```bash
pip install -i https://pypi.python.org/simple Musicreater pip install -i https://pypi.python.org/simple Musicreater
``` ```
- 对于开发者来说升级
- 升级
```bash ```bash
pip install -i https://pypi.python.org/simple Musicreater --upgrade pip install -i https://pypi.python.org/simple Musicreater --upgrade
``` ```
@@ -67,63 +63,63 @@
python setup.py install 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) [生成文件的使用](./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我的世界基岩版玩家喜欢编程和音乐深圳初二学生 诸葛亮与八卦阵 bgArray我的世界基岩版玩家喜欢编程和音乐深圳初二学生
## 致谢🙏 ## 致谢 🙏
本致谢列表排名无顺序 本致谢列表排名无顺序
- 感谢 **昀梦**\<QQ1515399885\> 找出指令生成错误bug并指正 - 感谢 **昀梦**\<QQ1515399885\> 找出指令生成错误 bug 并指正
- 感谢由 **Charlie_Ping 查理平** 带来的BDX文件转换参考以及MIDI-我的世界对应乐器参考表格 - 感谢由 **Charlie_Ping 查理平** 带来的 BDX 文件转换参考以及 MIDI-我的世界对应乐器 参考表格
- 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导同时我们参考了他的BDXworkshop作为BDX结构编辑的参考 - 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导同时我们参考了他的 BDXworkshop 作为 BDX 结构编辑的参考
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎**\<QQ1600515314\> 带来的midi音色解析以及转换指令的算法我们将其改编并应用同时感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力让我们在原本一骑绝尘的摸鱼道路上转向开发希望他能考上一个理想的大学 - 感谢由 **[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法我们将其改编并应用同时感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力让我们在原本一骑绝尘的摸鱼道路上转向开发希望他能考上一个理想的大学
- 感谢 **Touch 偷吃**\<QQ1793537164\> 提供的BDX导入测试支持并对程序的改进提供了丰富的意见同时也感谢他的不断尝试新的内容使我们的排错更进一步 - 感谢 **Touch 偷吃**\<QQ1793537164\> 提供的 BDX 导入测试支持并对程序的改进提供了丰富的意见同时也感谢他的不断尝试新的内容使我们的排错更进一步
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题辅助我们找到了视窗操作系统下的兼容性问题感谢其反馈延迟播放器出现的重大问题让我们得以修改全部延迟播放错误 - 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题辅助我们找到了视窗操作系统下的兼容性问题感谢其反馈延迟播放器出现的重大问题让我们得以修改全部延迟播放错误尤其感谢他对于我们的软件的大力宣传
- 感谢 **Ammelia 艾米利亚**\<QQ2838334637\> 敦促我们进行新的功能开发并为新功能提出了非常优秀的大量建议以及提供的BDX导入测试支持为我们的新结构生成算法提供了大量的实际理论支持 - 感谢 **Ammelia 艾米利亚**\<QQ2838334637\> 敦促我们进行新的功能开发并为新功能提出了非常优秀的大量建议以及提供的 BDX 导入测试支持为我们的新结构生成算法提供了大量的实际理论支持
- 感谢 **[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)** 对我们项目的支持与宣传希望他能考一所优秀的大学 - 感谢 **[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)** 对我们项目的支持与宣传希望他能考一所优秀的大学
- 感谢 **指令师_苦力怕 playjuice123**\<QQ240667197\>为我们的程序找出错误并提醒我们修复一个一直存在的大bug - 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误并提醒我们修复一个一直存在的大 bug
- 感谢 **雷霆**\<QQ3555268519\>为我们的程序找出错误并提醒修复bug - 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误并提醒修复 bug
- 感谢 **小埋**\<2039310975\> 反馈附加包生成时缺少描述和标题的问题
> 感谢广大群友为此程序提供的测试等支持 > 感谢广大群友为此程序提供的测试等支持
> >
> 若您对我们有所贡献但您的名字没有显示在此列表中请联系我们 > 若您对我们有所贡献但您的名字没有显示在此列表中请联系我们
## 联系📞 ## 联系 📞
若遇到库中的问题欢迎在[](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 微软 此项目不隶属或关联于 Mojang Studios 微软
此项目亦不 网易 相关 此项目亦不隶属或关联于 网易
Minecraft Mojang Synergies AB 的商标此项目中所有对于我的世界Minecraft等相关称呼均为引用性使用 Minecraft Mojang Synergies AB 的商标此项目中所有对于我的世界Minecraft等相关称呼均为引用性使用
* 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易网络科技发展有限公司 - 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易网络科技发展有限公司
NOT AN OFFICIAL MINECRAFT PRODUCT. NOT AN OFFICIAL MINECRAFT PRODUCT.
NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT. 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: 金羿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 [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"> <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> </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"> <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> <p>
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/) [![][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) [![CodeStyle: black]](https://github.com/psf/black)
![][python] [![][python]](https://www.python.org/)
[![][license]](LICENSE) [![][license]](LICENSE)
[![][release]](../../releases) [![][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🚀 ## 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) 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📄 ## Documentation📄
(Not in English yet) (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) [生成文件的使用](./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✒ ### Authors✒
Eilles (金羿)A senior high school student, individual developer, unfamous Bilibili UPer, which knows a little about commands in *Minecraft: Bedrock Edition* 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.
bgArray "诸葛亮与八卦阵": A junior high school student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
## Thanks🙏 ## Thanks🙏
This list is not in any order. This list is not in any order.
- Thank *昀梦*\<QQ1515399885\> for finding and correcting the bugs in the commands that *Musicreater* generated. - 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 _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 _[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 _[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 _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 _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 _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 _[神羽](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! > If you have given contribution but haven't been in the list, please contact us!
## 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). 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 AN OFFICIAL MINECRAFT PRODUCT.
NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
此项目并非一个官方 我的世界*Minecraft*项目 NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
此项目并非一个官方 我的世界_Minecraft_项目
此项目不隶属或关联于 Mojang Studios 微软 此项目不隶属或关联于 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: 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 [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 [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 [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

8
RSMC_test.py Normal file
View File

@@ -0,0 +1,8 @@
import Musicreater.experiment
print(
Musicreater.experiment.FutureMidiConvertRSNB.from_midi_file(
input("midi路径:"), old_exe_format=False
).to_note_list_in_delay()
)

View File

@@ -10,54 +10,131 @@
# 库的简单调用 # 库的简单调用
参见[example.py的相关部分](../example.py#L120)使用此库进行MIDI转换非常简单。 参见[example.py的相关部分](../example.py)使用此库进行MIDI转换非常简单
```python - 在导入转换库后使用 MidiConvert 类建立转换对象读取Midi文件
import Musicreater # 导入转换库
old_execute_format = False # 指定是否使用旧的execute指令语法即1.18及以前的《我的世界:基岩版》语法) ·创库支持新旧两种execute语法需要在对象实例化时指定
```python
# 导入音·创库
import Musicreater
# 首先新建转换对象。 # 指定是否使用旧的execute指令语法即1.18及以前的《我的世界:基岩版》语法)
conversion = Musicreater.midiConvert(enable_old_exe_format = old_execute_format) old_execute_format = False
# 值得注意的是,一个转换对象可以转换多个文件。
# 也就是在实例化的时候不进行对文件的绑定。
# 如果有调试需要,可以在实例化时传入参数 debug = True
# 如conversion = Musicreater.midiConvert(debug=True)
# 设置输入输出地址并指定execute指令语法 # 可以通过文件地址自动读取
# 地址都为字符串类型,不能传入文件流 cvt_mid = Musicreater.MidiConvert.from_midi_file(
midi_path = "./where/you/place/.midi/files.mid" "Midi文件地址",
output_folder = "./where/you/want2/convert/into/" old_exe_format=old_execute_format
)
# 设定基本转换参数 # 也可以导入Mido对象
conversion.convert(midi_path,output_folder) cvt_mid = Musicreater.MidiConvert(
mido.MidiFile("Midi文件地址"),
"音乐名称",
old_exe_format=old_execute_format
)
```
# 进行转换并接受输出,具体的参数均在代码之文档中有相关说明 - 获取 Midi 音乐经转换后的播放指令
method_id = 3 # 指定使用的转换算法
# 使用计分板播放器,转换为附加包文件 ```python
convertion_result = conversion.to_mcpack(method_id,*prompts) # 通过函数 to_command_list_in_score, to_command_list_in_delay
# 分别可以得到
# 以计分板作为播放器的指令对象列表、以延迟作为播放器的指令对象列表
# 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
# 使用延迟播放器,转换为附加包文件 # 使用 to_command_list_in_score 函数进行转换之后,返回值有三个
# 注意,在执行这个功能之前,你需要使用指令 # 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表
# pip install TrimMCStruct # 也就是列表套列表
# 安装最新版的TrimMCStruct库 # 但是,在对象内部所存储的数据却不会如此嵌套
convertion_result = conversion.to_mcpack_with_delay(method_id,*prompts) command_channel_list, command_count, max_score = cvt_mid.to_command_list_in_score(
"计分板名称",
1.0, # 音量比率
1.0, # 速度倍率
)
# 使用计分板播放器转换为BDX结构文件 # 使用 to_command_list_in_delay 转换后的返回值只有两个
convertion_result = conversion.to_BDX_file(method_id,*prompts) # 但是第一个返回值没有列表套列表
command_list, max_delay = cvt_mid.to_command_list_in_delay(
1.0, # 音量比率
1.0, # 速度倍率
"@a", # 玩家选择器
)
# 使用延迟播放器转换为BDX结构文件 # 运行之后,指令和总延迟会存储至对象内
convertion_result = conversion.to_BDX_file_with_delay(method_id,*prompts) print(
"音乐长度:{}/游戏刻".format(
cvt_mid.music_tick_num
)
)
print(
"指令如下:\n{}".format(
cvt_mid.music_command_list
)
)
```
# 转换结果是一个元组。 - 除了获取播放指令外还可以获取进度条指令
# 若其转换成功,则前三位必为
# True, 指令数量, 最大延迟 ```python
# 其中,最大延迟可以理解为计分板的最大值 # 通过函数 form_progress_bar 可以获得
# 如果转换失败,暂时还没有定返回值的规则 # 以计分板为载体所生成的进度条的指令对象列表
# 但是有一点是肯定的,数据结构必定是元组 # 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
print(convertion_result)
``` # 使用 form_progress_bar 函数进行转换之后,返回值有三个
# 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表
# 也就是列表套列表
cvt_mid.form_progress_bar(
max_score, # 音乐时长游戏刻
scoreboard_name, # 进度条使用的计分板名称
progressbar_style, # 进度条样式组(详见下方)
)
# 同上面生成播放指令的理,进度条指令也混存储至对象内
print(
"进度条指令如下:\n{}".format(
cvt_mid.progress_bar_command
)
)
```
在上面的代码中进度条样式是可以自定义的详见[下方说明](%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#进度条自定义)。
- 转换成指令是一个方面接下来是再转换为可以导入MC的格式我们提供了 · 内置的附加组件可以借助 `MidiConvert` 对象转换为相应格式
```python
# 导入 Musicreater
import Musicreater
# 导入附加组件功能
import Musicreater.plugin
# 导入相应的文件格式转换功能
# 转换为函数附加包
import Musicreater.plugin.funpack
# 转换为 BDX 结构文件
import Musicreater.plugin.bdxfile
# 转换为 mcstructure 结构文件
import Musicreater.plugin.mcstructfile
# 转换为结构附加包
import Musicreater.plugin.mcstructpack
# 直接通过 websocket 功能播放(正在开发)
import Musicreater.plugin.websocket
# 定义转换参数
cvt_cfg = Musicreater.plugin.ConvertConfig(
output_path,
volumn, # 音量大小参数
speed, # 速度倍率
progressbar, # 进度条样式组(详见下方)
)
# 懒得写了,等哪天有空写吧,后面的没用,是老的。
```
# 生成文件结构 # 生成文件结构
@@ -101,11 +178,11 @@ print(convertion_result)
所以结构的生成形状依照给定的高度和内含指令的数量决定 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整用数学公式的方式表达则是 所以结构的生成形状依照给定的高度和内含指令的数量决定 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整用数学公式的方式表达则是
$$MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor$$ $$ MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor $$
其中$MaxZ$即生成结构的$Z$轴最大延伸长度$NoC$表示链结构中所含指令方块的个数$MaxH$表示给定的生成结构的最大高度 其中$MaxZ$ 即生成结构的$Z$轴最大延伸长度$NoC$ 表示链结构中所含指令方块的个数$MaxH$ 表示给定的生成结构的最大高度
我们的结构生成器在生成指令链时将首先以相对坐标系 $(0, 0, 0)$ 即相对原点开始自下向上堆叠高度轴 $Y$ 的长当高度轴达到了限制的高度时便将 $Z$ 轴向正方向堆叠`1`个方块并开始自上向下重新堆叠直至高度轴坐标达到相对为`0`若当所生成结构的 $Z$ 轴长达到了其最大延伸长度则此结构生成器将反转 $Z$ 轴的堆叠方向直至 $Z$ 轴坐标相对为`0`如此往复直至指令链堆叠完成 我们的结构生成器在生成指令链时将首先以相对坐标系 $(0, 0, 0)$ 即相对原点开始自下向上堆叠高度轴 $Y$ 的长当高度轴达到了限制的高度时便将 $Z$ 轴向正方向堆叠`1`个方块并开始自上向下重新堆叠直至高度轴坐标达到相对为 `0`若当所生成结构的 $Z$ 轴长达到了其最大延伸长度则此结构生成器将反转 $Z$ 轴的堆叠方向直至 $Z$ 轴坐标相对为 `0`如此往复直至指令链堆叠完成
## 播放器 ## 播放器
@@ -120,7 +197,7 @@ print(convertion_result)
**·**用来达到这种效果的指令是这样的 **·**用来达到这种效果的指令是这样的
```mcfunction ```mcfunction
execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ~ ~Ht ~ Vlct Ptc execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
``` ```
|参数|说明|备注| |参数|说明|备注|
@@ -141,7 +218,7 @@ print(convertion_result)
在音·创中由于此方式播放的音乐不需要用计分板所以播放指令是这样的 在音·创中由于此方式播放的音乐不需要用计分板所以播放指令是这样的
```mcfunction ```mcfunction
execute Tg ~ ~ ~ playsound InstID @s ~ ~Ht ~ Vlct Ptc execute Tg ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
``` ```
|参数|说明|备注| |参数|说明|备注|
@@ -200,5 +277,5 @@ print(convertion_result)
`(r'%%N [ %%s/%^s %%% __________ %%t|%^t]',('§e=§r', '§7=§r'))` `(r'%%N [ %%s/%^s %%% __________ %%t|%^t]',('§e=§r', '§7=§r'))`
*对了为了避免生成错误请尽量避免使用标识符作为定义样式字符串的其他部分* *为了避免生成错误请尽量避免使用标识符作为定义样式字符串的其他部分*

View File

@@ -10,20 +10,30 @@
*由于先前的 **读我文件**(README.md) 过于冗杂现另辟蹊径来给大家全方位的教程* *由于先前的 **读我文件**(README.md) 过于冗杂现另辟蹊径来给大家全方位的教程*
*这是本库所生成文件的使用声明不是使用本库的教程若要查看**本库的演示程序**使用教程可点击[此处](%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md)若要查看有关文件结构的内容可以点击[此处](./%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E.md)* *这是本库所生成文件的使用声明不是使用本库的教程若要查看**本库的大致文档**可点击[此处](./%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)若要查看有关文件结构的内容可以点击[此处](./%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E.md)*
## 附加包格式 ## 附加包格式
支持的文件后缀`.MCPACK` 支持的文件后缀`.MCPACK`
1. 导入附加包 - 计分板播放器
2. 在一个循环方块中输入指令 `function index`
3. 将需要聆听音乐的实体的播放所用计分板设置为 `1`
4. 激活循环方块
5. 若想要暂停播放可以停止循环指令方块的激活状态
6. 若想要重置某实体的播放可以将其播放用的计分板重置
> 其中 步骤三 步骤四 的顺序可以调换 1. 导入附加包
2. 在一个循环方块中输入指令 `function index`
3. 将需要聆听音乐的实体的播放所用计分板设置为 `1`
4. 激活循环方块
5. 若想要暂停播放可以停止循环指令方块的激活状态
6. 若想要重置某实体的播放可以将其播放用的计分板重置
> 其中 步骤三 步骤四 的顺序可以调换
- 延迟播放器
1. 导入附加包
2. 在聊天框输入指令 `function index`
3. 同时激活所生成的循环和脉冲指令方块
> 需要注意的是循环指令方块需要一直激活直到音乐结束
## 结构格式 ## 结构格式
@@ -41,7 +51,7 @@
- 计分板播放器 - 计分板播放器
2. 在所生成的第一个指令方块前放置一个循环指令方块其朝向应当对着所生成的第一个方块 2. 在所生成的第一个指令方块前放置一个循环指令方块其朝向应当对着所生成的第一个方块
3. 在循环指令方块中输入使播放对象的播放用计分板加分的指令延迟为`0`每次循环增加`1` 3. 在循环指令方块中输入使播放对象的播放用计分板加分的指令延迟为 `0`每次循环增加 `1`
4. 激活循环方块 4. 激活循环方块
5. 若想要暂停播放可以停止循环指令方块的激活状态 5. 若想要暂停播放可以停止循环指令方块的激活状态
6. 若想要重置某实体的播放可以将其播放用的计分板重置 6. 若想要重置某实体的播放可以将其播放用的计分板重置

View File

@@ -1,2 +1,198 @@
**_注意本文档中的对照表版权归属于音·创作者并按照本仓库根目录下 LICENSE.md 中规定开源_**
# 暂无 **_使用时请遵循规定_**
- 版权所有 © 2023 · 开发者
- Copyright © 2023 all the developers of Musicreater
* 开源相关声明请见 仓库根目录下的 License.md
* Terms & Conditions: License.md in the root directory
· 开发交流群 861684859\
Email TriM-Organization@hotmail.com\
若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
# 乐音乐器
| Midi 乐器值 | 我的世界音符名称 | 我的世界音调偏移参数 |
| ----------- | ------------------- | -------------------- |
| 0 | note.harp | 6 |
| 1 | note.harp | 6 |
| 2 | note.pling | 6 |
| 3 | note.harp | 6 |
| 4 | note.pling | 6 |
| 5 | note.pling | 6 |
| 6 | note.harp | 6 |
| 7 | note.harp | 6 |
| 8 | note.share | 7 |
| 9 | note.harp | 6 |
| 10 | note.didgeridoo | 8 |
| 11 | note.harp | 6 |
| 12 | note.xylophone | 4 |
| 13 | note.chime | 4 |
| 14 | note.harp | 6 |
| 15 | note.harp | 6 |
| 16 | note.bass | 8 |
| 17 | note.harp | 6 |
| 18 | note.harp | 6 |
| 19 | note.harp | 6 |
| 20 | note.harp | 6 |
| 21 | note.harp | 6 |
| 22 | note.harp | 6 |
| 23 | note.guitar | 7 |
| 24 | note.guitar | 7 |
| 25 | note.guitar | 7 |
| 26 | note.guitar | 7 |
| 27 | note.guitar | 7 |
| 28 | note.guitar | 7 |
| 29 | note.guitar | 7 |
| 30 | note.guitar | 7 |
| 31 | note.bass | 8 |
| 32 | note.bass | 8 |
| 33 | note.bass | 8 |
| 34 | note.bass | 8 |
| 35 | note.bass | 8 |
| 36 | note.bass | 8 |
| 37 | note.bass | 8 |
| 38 | note.bass | 8 |
| 39 | note.bass | 8 |
| 40 | note.harp | 6 |
| 41 | note.harp | 6 |
| 42 | note.harp | 6 |
| 43 | note.harp | 6 |
| 44 | note.iron_xylophone | 6 |
| 45 | note.guitar | 7 |
| 46 | note.harp | 6 |
| 47 | note.harp | 6 |
| 48 | note.guitar | 7 |
| 49 | note.guitar | 7 |
| 50 | note.bit | 6 |
| 51 | note.bit | 6 |
| 52 | note.harp | 6 |
| 53 | note.harp | 6 |
| 54 | note.bit | 6 |
| 55 | note.flute | 5 |
| 56 | note.flute | 5 |
| 57 | note.flute | 5 |
| 58 | note.flute | 5 |
| 59 | note.flute | 5 |
| 60 | note.flute | 5 |
| 61 | note.flute | 5 |
| 62 | note.flute | 5 |
| 63 | note.flute | 5 |
| 64 | note.bit | 6 |
| 65 | note.bit | 6 |
| 66 | note.bit | 6 |
| 67 | note.bit | 6 |
| 68 | note.flute | 5 |
| 69 | note.harp | 6 |
| 70 | note.harp | 6 |
| 71 | note.flute | 5 |
| 72 | note.flute | 5 |
| 73 | note.flute | 5 |
| 74 | note.harp | 6 |
| 75 | note.flute | 5 |
| 76 | note.harp | 6 |
| 77 | note.harp | 6 |
| 78 | note.harp | 6 |
| 79 | note.harp | 6 |
| 80 | note.bit | 6 |
| 81 | note.bit | 6 |
| 82 | note.bit | 6 |
| 83 | note.bit | 6 |
| 84 | note.bit | 6 |
| 85 | note.bit | 6 |
| 86 | note.bit | 6 |
| 87 | note.bit | 6 |
| 88 | note.bit | 6 |
| 89 | note.bit | 6 |
| 90 | note.bit | 6 |
| 91 | note.bit | 6 |
| 92 | note.bit | 6 |
| 93 | note.bit | 6 |
| 94 | note.bit | 6 |
| 95 | note.bit | 6 |
| 96 | note.bit | 6 |
| 97 | note.bit | 6 |
| 98 | note.bit | 6 |
| 99 | note.bit | 6 |
| 100 | note.bit | 6 |
| 101 | note.bit | 6 |
| 102 | note.bit | 6 |
| 103 | note.bit | 6 |
| 104 | note.harp | 6 |
| 105 | note.banjo | 6 |
| 106 | note.harp | 6 |
| 107 | note.harp | 6 |
| 108 | note.harp | 6 |
| 109 | note.harp | 6 |
| 110 | note.harp | 6 |
| 111 | note.guitar | 7 |
| 112 | note.harp | 6 |
| 113 | note.bell | 4 |
| 114 | note.harp | 6 |
| 115 | note.cow_bell | 5 |
| 116 | note.bd | 7 |
| 117 | note.bass | 8 |
| 118 | note.bit | 6 |
| 119 | note.bd | 7 |
| 120 | note.guitar | 7 |
| 121 | note.harp | 6 |
| 122 | note.harp | 6 |
| 123 | note.harp | 6 |
| 124 | note.harp | 6 |
| 125 | note.hat | 7 |
| 126 | note.bd | 7 |
| 127 | note.snare | 7 |
# 打击乐器
| Midi 打击乐器值 | 我的世界音符名称 | 我的世界音调偏移参数 |
| --------------- | ------------------- | -------------------- |
| 34 | note.bd | 7 |
| 35 | note.bd | 7 |
| 36 | note.hat | 7 |
| 37 | note.snare | 7 |
| 38 | note.snare | 7 |
| 39 | note.snare | 7 |
| 40 | note.hat | 7 |
| 41 | note.snare | 7 |
| 42 | note.hat | 7 |
| 43 | note.snare | 7 |
| 44 | note.snare | 7 |
| 45 | note.bell | 4 |
| 46 | note.snare | 7 |
| 47 | note.snare | 7 |
| 48 | note.bell | 4 |
| 49 | note.hat | 7 |
| 50 | note.bell | 4 |
| 51 | note.bell | 4 |
| 52 | note.bell | 4 |
| 53 | note.bell | 4 |
| 54 | note.bell | 4 |
| 55 | note.bell | 4 |
| 56 | note.snare | 7 |
| 57 | note.hat | 7 |
| 58 | note.chime | 4 |
| 59 | note.iron_xylophone | 6 |
| 60 | note.bd | 7 |
| 61 | note.bd | 7 |
| 62 | note.xylophone | 4 |
| 63 | note.xylophone | 4 |
| 64 | note.xylophone | 4 |
| 65 | note.hat | 7 |
| 66 | note.bell | 4 |
| 67 | note.bell | 4 |
| 68 | note.hat | 7 |
| 69 | note.hat | 7 |
| 70 | note.flute | 5 |
| 71 | note.flute | 5 |
| 72 | note.hat | 7 |
| 73 | note.hat | 7 |
| 74 | note.xylophone | 4 |
| 75 | note.hat | 7 |
| 76 | note.hat | 7 |
| 77 | note.xylophone | 4 |
| 78 | note.xylophone | 4 |
| 79 | note.bell | 4 |
| 80 | note.bell | 4 |

View File

@@ -17,8 +17,12 @@ Terms & Conditions: ./License.md
""" """
import os import os
import Musicreater
import Musicreater
from Musicreater.plugin import ConvertConfig
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
from Musicreater.plugin.funcpack import to_function_addon_in_score
from Musicreater.plugin.mcstructpack import to_mcstructure_addon_in_delay
# 获取midi列表 # 获取midi列表
midi_path = input(f"请输入MIDI路径") midi_path = input(f"请输入MIDI路径")
@@ -27,17 +31,6 @@ midi_path = input(f"请输入MIDI路径")
# 获取输出地址 # 获取输出地址
out_path = input(f"请输入输出路径:") out_path = input(f"请输入输出路径:")
conversion = Musicreater.midiConvert()
def isMethodOK(sth: str):
if int(sth) in range(1, len(conversion.methods) + 1):
return int(sth)
else:
raise ValueError
convert_method = int(input(f"请输入转换算法[1~{len(conversion.methods)}]"))
# 选择输出格式 # 选择输出格式
fileFormat = int(input(f"请输入输出格式[BDX(1) 或 MCPACK(0)]").lower()) fileFormat = int(input(f"请输入输出格式[BDX(1) 或 MCPACK(0)]").lower())
@@ -57,15 +50,10 @@ def bool_str(sth: str) -> bool:
raise ValueError("布尔字符串啊?") raise ValueError("布尔字符串啊?")
debug = False
if os.path.exists("./demo_config.json"): if os.path.exists("./demo_config.json"):
import json import json
prompts = json.load(open("./demo_config.json", "r", encoding="utf-8")) prompts = json.load(open("./demo_config.json", "r", encoding="utf-8"))
if prompts[-1] == "debug":
debug = True
prompts = prompts[:-1]
else: else:
prompts = [] prompts = []
# 提示语 检测函数 错误提示语 # 提示语 检测函数 错误提示语
@@ -103,41 +91,39 @@ else:
) )
if fileFormat == 1 if fileFormat == 1
else (), else (),
( ()
if playerFormat == 1
else (
f"最大结构高度:", f"最大结构高度:",
int, int,
) ),
if fileFormat == 1
else (),
]: ]:
if args: if args:
prompts.append(args[1](input(args[0]))) prompts.append(args[1](input(args[0])))
conversion = Musicreater.midiConvert(debug=debug)
print(f"正在处理 {midi_path} ") print(f"正在处理 {midi_path} ")
conversion.convert(midi_path, out_path) cvt_mid = Musicreater.MidiConvert.from_midi_file(midi_path, old_exe_format=False)
if debug: cvt_cfg = ConvertConfig(out_path, *prompts[:3])
with open("./records.json", "a", encoding="utf-8") as f:
json.dump(conversion.toDICT(), f) print(
f.write(5 * "\n") " 指令总长:{},最高延迟:{}".format(
conversion_result = ( *(
conversion.to_mcpack(convert_method, *prompts) to_function_addon_in_score(cvt_mid, cvt_cfg, *prompts[3:])
if fileFormat == 0
else (
conversion.to_BDX_file(convert_method, *prompts)
if playerFormat == 1 if playerFormat == 1
else conversion.to_BDX_file_with_delay(convert_method, *prompts) else to_mcstructure_addon_in_delay(cvt_mid, cvt_cfg, *prompts[3:])
)
)
if fileFormat == 0
else " 指令总长:{},最高延迟:{},结构大小{},终点坐标{}".format(
*(
to_BDX_file_in_score(cvt_mid, cvt_cfg, *prompts[3:])
if playerFormat == 1
else to_BDX_file_in_delay(cvt_mid, cvt_cfg, *prompts[3:])
)
) )
) )
if conversion_result[0]:
print(
f" 指令总长:{conversion_result[1]},最高延迟:{conversion_result[2]}{f''',结构大小{conversion_result[3]},最末坐标{conversion_result[4]}''' if fileFormat == 1 else ''}"
)
else:
print(f"失败:{conversion_result}")
exitSth = input("回车退出").lower() exitSth = input("回车退出").lower()
if exitSth == "record": if exitSth == "record":

13
example_futureFunction.py Normal file
View File

@@ -0,0 +1,13 @@
import Musicreater.experiment
import Musicreater.plugin
import Musicreater.plugin.mcstructpack
print(
Musicreater.plugin.mcstructpack.to_mcstructure_addon_in_delay(
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(input("midi路径:"), old_exe_format=False),
Musicreater.plugin.ConvertConfig(
input("输出路径:"),
volume=1
),
)
)

View File

@@ -1,8 +1,13 @@
from Musicreater import midiConvert import Musicreater
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
conversion = midiConvert(enable_old_exe_format=False) print(
conversion.convert(input("midi路径:"), input("输出路径:")) Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
Musicreater.MidiConvert.from_midi_file(input("midi路径:"), old_exe_format=False),
conversion.to_mcstructure_file_with_delay( Musicreater.plugin.ConvertConfig(
3, input("输出路径:"),
volume=1
),
)
) )

View File

@@ -0,0 +1,13 @@
import Musicreater
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
print(
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_redstone_CD(
Musicreater.MidiConvert.from_midi_file(input("midi路径:"), old_exe_format=False),
Musicreater.plugin.ConvertConfig(
input("输出路径:"),
volume=1
),
)
)

View File

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

View File

@@ -5,7 +5,7 @@ import Musicreater
with open("requirements.txt", "r", encoding="utf-8") as fh: with open("requirements.txt", "r", encoding="utf-8") as fh:
dependences = fh.read().strip().split("\n") dependences = fh.read().strip().split("\n")
with open("README.md", "r", encoding="utf-8") as fh: with open("README_EN.md", "r", encoding="utf-8") as fh:
long_description = fh.read().replace( long_description = fh.read().replace(
"./docs/", "https://github.com/TriM-Organization/Musicreater/blob/master/docs/" "./docs/", "https://github.com/TriM-Organization/Musicreater/blob/master/docs/"
) )
@@ -29,8 +29,6 @@ setuptools.setup(
"Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",

View File

@@ -1,3 +1,3 @@
python -m build python setup.py sdist bdist_wheel
python -m twine upload dist/* python -m twine upload dist/*
python clean_update.py python clean_update.py

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env bash
python -m build
python -m twine upload dist/*
python clean_update.py

View File

@@ -1,3 +0,0 @@
python setup.py sdist bdist_wheel
python -m twine upload dist/*
python clean_update.py