史诗级更新!支持导出mcstructure库,更改部分API和文档,修改了部分代码格式,新增红乐测试

This commit is contained in:
EillesWan 2023-04-29 17:00:32 +08:00
parent b758a2f967
commit 4874ace92d
13 changed files with 905 additions and 357 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@
*.mcpack
*.bdx
*.json
*.mcstructure
# Byte-compiled / optimized
__pycache__/

View File

@ -19,8 +19,6 @@ Terms & Conditions: ../License.md
from .main import *
__version__ = "0.4.0"
__version__ = "0.5.0"
__all__ = []
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"))
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon"))

View File

@ -25,6 +25,7 @@ class MSCTBaseException(Exception):
"""音·创库版本的所有错误均继承于此"""
def __init__(self, *args):
"""音·创库版本的所有错误均继承于此"""
super().__init__(*args)
def miao(
@ -37,37 +38,65 @@ class MSCTBaseException(Exception):
raise self
class CrossNoteError(MSCTBaseException):
"""同通道下同音符交叉出现所产生的错误"""
class MidiFormatException(MSCTBaseException):
"""音·创库版本的所有MIDI格式错误均继承于此"""
pass
class NotDefineTempoError(MSCTBaseException):
"""没有Tempo设定导致时间无法计算的错误"""
pass
def __init__(self, *args):
"""音·创库版本的所有MIDI格式错误均继承于此"""
super().__init__("MIDI格式错误", *args)
class MidiDestroyedError(MSCTBaseException):
"""Midi文件损坏"""
pass
def __init__(self, *args):
"""Midi文件损坏"""
super().__init__("MIDI文件损坏无法读取MIDI文件", *args)
class ChannelOverFlowError(MSCTBaseException):
"""一个midi中含有过多的通道数量应≤16"""
class CommandFormatError(RuntimeError):
"""指令格式与目标格式不匹配而引起的错误"""
pass
def __init__(self, *args):
"""指令格式与目标格式不匹配而引起的错误"""
super().__init__("指令格式不匹配", *args)
class NotDefineProgramError(MSCTBaseException):
class CrossNoteError(MidiFormatException):
"""同通道下同音符交叉出现所产生的错误"""
def __init__(self, *args):
"""同通道下同音符交叉出现所产生的错误"""
super().__init__("同通道下同音符交叉", *args)
class NotDefineTempoError(MidiFormatException):
"""没有Tempo设定导致时间无法计算的错误"""
def __init__(self, *args):
"""没有Tempo设定导致时间无法计算的错误"""
super().__init__("在曲目开始时没有声明Tempo未指定拍长", *args)
class ChannelOverFlowError(MidiFormatException):
"""一个midi中含有过多的通道"""
def __init__(self, max_channel = 16, *args):
"""一个midi中含有过多的通道"""
super().__init__("含有过多的通道(数量应≤{}".format(max_channel), *args)
class NotDefineProgramError(MidiFormatException):
"""没有Program设定导致没有乐器可以选择的错误"""
pass
def __init__(self, *args):
"""没有Program设定导致没有乐器可以选择的错误"""
super().__init__("未指定演奏乐器", *args)
class ZeroSpeedError(MSCTBaseException):
class ZeroSpeedError(MidiFormatException):
"""以0作为播放速度的错误"""
pass
def __init__(self, *args):
"""以0作为播放速度的错误"""
super().__init__("播放速度为0", *args)

View File

@ -176,4 +176,25 @@ percussion_instrument_list = {
77: ("note.xylophone", 4),
78: ("note.xylophone", 4),
79: ("note.bell", 4),
80: ("note.bell", 4), }
80: ("note.bell", 4),
}
instrument_to_blocks_list = {
"note.bass": ("planks",),
"note.snare": ("sand",),
"note.hat": ("glass",),
"note.bd": ("stone",),
"note.bell": ("gold_block",),
"note.flute": ("clay",),
"note.chime": ("packed_ice",),
"note.guitar": ("wool",),
"note.xylobone": ("bone_block",),
"note.iron_xylophone": ("iron_block",),
"note.cow_bell": ("soul_sand",),
"note.didgeridoo": ("pumpkin",),
"note.bit": ("emerald_block",),
"note.banjo": ("hay_block",),
"note.pling": ("glowstone",),
"note.bassattack": ("command_block",), # 无法找到此音效
"note.harp": ("glass",),
}

View File

@ -1,35 +1,38 @@
# -*- coding: utf-8 -*-
"""
功能测试 若非已知 请勿更改
此文件仅供功能测试并非实际调用的文件
请注意此处的文件均为测试使用
不要更改 不要更改 不要更改
请注意这里的一切均需要其原作者更改
这里用于放置一些新奇的点子
用于测试
不要更改 不要更改 不要更改
"""
# 音·创 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
# 若需使用或借鉴 请依照 Apache 2.0 许可证进行许可
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
"""
· 库版 (Musicreater Package Version)
是一款免费开源的针对我的世界基岩版的midi音乐转换库
注意除了此源文件以外任何属于此仓库以及此项目的文件均依照Apache许可证进行许可
Musicreater pkgver (Package Version · 库版)
A free open source library used for convert midi file into formats that is suitable for **Minecraft: Bedrock Edition**.
Note! Except for this source file, all the files in this repository and this project are licensed under Apache License 2.0
· (Musicreater)
是一款免费开源的针对我的世界的midi音乐转换库
Musicreater (·)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
Copyright 2022 all the developers of Musicreater
版权所有 © 2023 · 开发者
Copyright © 2023 all the developers of Musicreater
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
开源相关声明请见 ../License.md
Terms & Conditions: ../License.md
"""
def _toCmdList_m1(
self,
scoreboardname: str = "mscplay",
@ -91,10 +94,6 @@ def _toCmdList_m1(
# ============================
@ -102,10 +101,6 @@ def _toCmdList_m1(
import mido
class NoteMessage:
def __init__(self, channel, pitch, velocity, startT, lastT, midi, now_bpm, change_bpm=None):
self.channel = channel
@ -213,5 +208,98 @@ if __name__ == '__main__':
# ============================
from typing import Union
from .utils import x,y,z,bottem_side_length_of_smallest_square_bottom_box,form_note_block_in_NBT_struct,form_repeater_in_NBT_struct
# 不要用 没写完
def delay_to_note_blocks(
baseblock: str = "stone",
position_forward: Union(x, y, z) = z,
max_height: int = 64,
):
"""传入音符,生成以音符盒存储的红石音乐
:param:
baseblock: 中继器的下垫方块
position_forward: 结构延长方向
:return 是否生成成功
"""
from TrimMCStruct import Structure, Block
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
len(commands), max_height
)
struct = Structure(
(_sideLength, max_height, _sideLength), # 声明结构大小
)
log = print
startpos = [0,0,0]
# 1拍 x 2.5 rt
def placeNoteBlock():
for i in notes:
error = True
try:
struct.set_block(
[startpos[0], startpos[1] + 1, startpos[2]],
form_note_block_in_NBT_struct(height2note[i[0]], instrument),
)
struct.set_block(startpos, Block("universal_minecraft", instuments[i[0]][1]),)
error = False
except ValueError:
log("无法放置音符:" + str(i) + "" + str(startpos))
struct.set_block(Block("universal_minecraft", baseblock), startpos)
struct.set_block(
Block("universal_minecraft", baseblock),
[startpos[0], startpos[1] + 1, startpos[2]],
)
finally:
if error is True:
log("无法放置音符:" + str(i) + "" + str(startpos))
struct.set_block(Block("universal_minecraft", baseblock), startpos)
struct.set_block(
Block("universal_minecraft", baseblock),
[startpos[0], startpos[1] + 1, startpos[2]],
)
delay = int(i[1] * speed + 0.5)
if delay <= 4:
startpos[0] += 1
struct.set_block(
form_repeater_in_NBT_struct(delay, "west"),
[startpos[0], startpos[1] + 1, startpos[2]],
)
struct.set_block(Block("universal_minecraft", baseblock), startpos)
else:
for j in range(int(delay / 4)):
startpos[0] += 1
struct.set_block(
form_repeater_in_NBT_struct(4, "west"),
[startpos[0], startpos[1] + 1, startpos[2]],
)
struct.set_block(Block("universal_minecraft", baseblock), startpos)
if delay % 4 != 0:
startpos[0] += 1
struct.set_block(
form_repeater_in_NBT_struct(delay % 4, "west"),
[startpos[0], startpos[1] + 1, startpos[2]],
)
struct.set_block(Block("universal_minecraft", baseblock), startpos)
startpos[0] += posadder[0]
startpos[1] += posadder[1]
startpos[2] += posadder[2]
# e = True
try:
placeNoteBlock()
# e = False
except: # ValueError
log("无法放置方块了,可能是因为区块未加载叭")

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,9 @@
import math
import os
from typing import Union
from TrimMCStruct import Structure, Block, TAG_Long, TAG_Byte
key = {
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"],
@ -18,7 +20,7 @@ def bdx_move(axis: str, value: int):
if value == 0:
return b""
if abs(value) == 1:
return key[axis][0 if value == -1 else 1]
return bdx_key[axis][0 if value == -1 else 1]
pointer = sum(
[
@ -32,7 +34,9 @@ def bdx_move(axis: str, value: int):
]
)
return key[axis][pointer] + value.to_bytes(2 ** (pointer - 2), "big", signed=True)
return bdx_key[axis][pointer] + value.to_bytes(
2 ** (pointer - 2), "big", signed=True
)
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
@ -112,8 +116,7 @@ def form_command_block_in_BDX_bytes(
:return:str
"""
block = b"\x24" + \
particularValue.to_bytes(2, byteorder="big", signed=False)
block = b"\x24" + particularValue.to_bytes(2, byteorder="big", signed=False)
for i in [
impluse.to_bytes(4, byteorder="big", signed=False),
@ -138,7 +141,7 @@ def bottem_side_length_of_smallest_square_bottom_box(total: int, maxHeight: int)
return math.ceil(math.sqrt(math.ceil(total / maxHeight)))
def to_BDX_bytes(
def commands_to_BDX_bytes(
commands: list,
max_height: int = 64,
):
@ -178,7 +181,7 @@ def to_BDX_bytes(
else (3 if z_forward else 2)
if (
((now_z != 0) and (not z_forward))
or (z_forward and (now_z != _sideLength))
or (z_forward and (now_z != _sideLength - 1))
)
else 5,
impluse=impluse,
@ -204,15 +207,13 @@ def to_BDX_bytes(
):
now_z -= 1 if z_forward else -1
z_forward = not z_forward
_bytes += key[x][1]
_bytes += bdx_key[x][1]
now_x += 1
else:
_bytes += key[z][int(z_forward)]
_bytes += bdx_key[z][int(z_forward)]
else:
_bytes += key[y][int(y_forward)]
_bytes += bdx_key[y][int(y_forward)]
return (
_bytes,
@ -225,6 +226,60 @@ def to_BDX_bytes(
)
def form_note_block_in_NBT_struct(
note: int, coordinate: tuple, instrument: str = "note.harp", powered: bool = False
):
"""生成音符盒方块
:param note: `int`(0~24)
音符的音高
:param coordinate: `tuple[int,int,int]`
此方块所在之相对坐标
:param instrument: `str`
音符盒的乐器
:param powered: `bool`
是否已被激活
:return Block
"""
return Block(
"minecraft",
"noteblock",
{
"instrument": instrument.replace("note.", ""),
"note": note,
"powered": powered,
},
{
"block_entity_data": {
"note": TAG_Byte(note),
"id": "noteblock",
"x": coordinate[0],
"y": coordinate[1],
"z": coordinate[2],
}
},
)
def form_repeater_in_NBT_struct(
delay: int, facing: int
):
"""生成中继器方块
:param powered:
:param locked:
:param facing:
:param delay: 1~4
:return Block()"""
return Block(
"minecraft",
"unpowered_repeater",
{
"repeater_delay": delay,
"direction": facing,
},
)
def form_command_block_in_NBT_struct(
command: str,
coordinate: tuple,
@ -281,42 +336,43 @@ def form_command_block_in_NBT_struct(
:return:str
"""
from TrimMCStruct import Block, TAG_Long
block = Block(
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},
"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={
'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],
"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,
)
return block
def to_structure(
def commands_to_structure(
commands: list,
max_height: int = 64,
):
@ -325,8 +381,6 @@ def to_structure(
:param max_height: 生成结构最大高度
:return 成功与否成功返回(结构类,结构占用大小)失败返回(False,str失败原因)
"""
# 导入库
from TrimMCStruct import Structure
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
len(commands), max_height
@ -345,7 +399,8 @@ def to_structure(
for cmd, delay in commands:
coordinate = (now_x, now_y, now_z)
struct.set_block(coordinate,
struct.set_block(
coordinate,
form_command_block_in_NBT_struct(
command=cmd,
coordinate=coordinate,
@ -354,12 +409,14 @@ def to_structure(
((now_y != 0) and (not y_forward))
or (y_forward and (now_y != (max_height - 1)))
)
else (3 if z_forward else 2)
else (
(3 if z_forward else 2)
if (
((now_z != 0) and (not z_forward))
or (z_forward and (now_z != _sideLength))
or (z_forward and (now_z != _sideLength - 1))
)
else 5,
else 5
),
impluse=2,
condition=False,
alwaysRun=True,
@ -367,7 +424,8 @@ def to_structure(
customName="",
executeOnFirstTick=False,
trackOutput=True,
))
),
)
now_y += 1 if y_forward else -1
@ -387,9 +445,11 @@ def to_structure(
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),
)

View File

@ -4,7 +4,7 @@
</h1>
<p align="center">
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.ico">
</img>
</p>

View File

@ -4,7 +4,7 @@
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
</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 midi files into _Minecraft_ formats.</h3>
<p align="center">
<img src="https://forthebadge.com/images/badges/built-with-love.svg">

View File

@ -15,8 +15,10 @@
```python
import Musicreater # 导入转换库
old_execute_format = False # 指定是否使用旧的execute指令语法即1.18及以前的《我的世界:基岩版》语法)
# 首先新建转换对象。
conversion = Musicreater.midiConvert()
conversion = Musicreater.midiConvert(enable_old_exe_format = old_execute_format)
# 值得注意的是,一个转换对象可以转换多个文件。
# 也就是在实例化的时候不进行对文件的绑定。
# 如果有调试需要,可以在实例化时传入参数 debug = True
@ -26,10 +28,9 @@ conversion = Musicreater.midiConvert()
# 地址都为字符串类型,不能传入文件流
midi_path = "./where/you/place/.midi/files.mid"
output_folder = "./where/you/want2/convert/into/"
old_execute_format = False # 指定是否使用旧的execute指令语法即1.18及以前的《我的世界:基岩版》语法)
# 设定基本转换参数
conversion.convert(midi_path,output_folder,old_execute_format)
conversion.convert(midi_path,output_folder)
# 进行转换并接受输出,具体的参数均在代码之文档中有相关说明
method_id = 3 # 指定使用的转换算法

View File

@ -20,8 +20,6 @@ import os
import Musicreater
# 获取midi列表
midi_path = input(f"请输入MIDI路径")
@ -46,9 +44,6 @@ fileFormat = int(input(f"请输入输出格式[BDX(1) 或 MCPACK(0)]").lower(
playerFormat = int(input(f"请选择播放方式[计分板(1) 或 延迟(0)]").lower())
# 真假字符串判断
def bool_str(sth: str) -> bool:
try:
@ -61,6 +56,7 @@ def bool_str(sth: str) -> bool:
else:
raise ValueError("布尔字符串啊?")
debug = False
if os.path.exists("./demo_config.json"):
@ -75,40 +71,40 @@ else:
# 提示语 检测函数 错误提示语
for args in [
(
f'输入音量:',
f"输入音量:",
float,
),
(
f'输入播放速度:',
f"输入播放速度:",
float,
),
(
f'是否启用进度条:',
f"是否启用进度条:",
bool_str,
),
(
f'计分板名称:',
f"计分板名称:",
str,
)
if playerFormat == 1
else (
f'玩家选择器:',
f"玩家选择器:",
str,
),
(
f'是否自动重置计分板:',
f"是否自动重置计分板:",
bool_str,
)
if playerFormat == 1
else (),
(
f'作者名称:',
f"作者名称:",
str,
)
if fileFormat == 1
else (),
(
f'最大结构高度:',
f"最大结构高度:",
int,
)
if fileFormat == 1
@ -117,7 +113,7 @@ else:
if args:
prompts.append(args[1](input(args[0])))
conversion = Musicreater.midiConvert(debug)
conversion = Musicreater.midiConvert(debug=debug)
print(f"正在处理 {midi_path} ")

View File

@ -1,6 +1,8 @@
from Musicreater import midiConvert
conversion = midiConvert()
conversion.convert(input("midi path:"),input("out path:"))
conversion.to_mcstructure_file_with_delay(3,)
conversion = midiConvert(enable_old_exe_format=False)
conversion.convert(input("midi路径:"), input("输出路径:"))
conversion.to_mcstructure_file_with_delay(
3,
)

View File

@ -1,3 +1,3 @@
Brotli>=1.0.9
mido>=1.2.10
TrimMCStruct>=0.0.5.5
TrimMCStruct>=0.0.5.6