mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-27 11:45:39 +00:00
Compare commits
18 Commits
v3.0.0-alp
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de830262a7 | ||
|
|
9c1383360b | ||
|
|
60cbdfb9d0 | ||
|
|
d6944392cd | ||
|
|
ba7b10a25f | ||
|
|
307feb9b24 | ||
|
|
6e518dada4 | ||
|
|
4c036cbd4c | ||
|
|
c310ba5dc3 | ||
|
|
d9c92fa269 | ||
|
|
00c445f7ad | ||
|
|
3ee686c712 | ||
|
|
0e95a1e541 | ||
|
|
bbc67921d6 | ||
|
|
62cd4a0c94 | ||
|
|
295da53c60 | ||
|
|
fff8e43f53 | ||
|
|
2a5ccb8eeb |
@@ -5,7 +5,7 @@
|
|||||||
是一款免费开源的《我的世界》数字音频支持库。
|
是一款免费开源的《我的世界》数字音频支持库。
|
||||||
|
|
||||||
Musicreater (音·创)
|
Musicreater (音·创)
|
||||||
A free and open-source library for handling with **Minecraft** digital music.
|
A cost free and open-source library for handling with **Minecraft** digital music.
|
||||||
|
|
||||||
版权所有 © 2026 睿乐组织
|
版权所有 © 2026 睿乐组织
|
||||||
Copyright © 2026 TriM-Organization
|
Copyright © 2026 TriM-Organization
|
||||||
@@ -33,7 +33,6 @@ __author__ = (
|
|||||||
("金羿", "Eilles"),
|
("金羿", "Eilles"),
|
||||||
("玉衡Alioth", "YuhengAlioth"),
|
("玉衡Alioth", "YuhengAlioth"),
|
||||||
("鱼旧梦", "ElapsingDreams"),
|
("鱼旧梦", "ElapsingDreams"),
|
||||||
("偷吃不是Touch", "Touch"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .paramcurve import ParamCurve, InterpolationMethod, BoundaryBehaviour
|
from .paramcurve import ParamCurve, InterpolationMethod, BoundaryBehaviour
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 的插件基类,提供抽象接口以供实际插件使用
|
音·创 v3 的插件基类,提供抽象接口以供实际插件使用
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -41,6 +41,8 @@ from typing import (
|
|||||||
Generator,
|
Generator,
|
||||||
Iterator,
|
Iterator,
|
||||||
Set,
|
Set,
|
||||||
|
Type,
|
||||||
|
Mapping,
|
||||||
)
|
)
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
if sys.version_info >= (3, 11):
|
||||||
@@ -97,6 +99,7 @@ from .data import SingleMusic, SingleTrack
|
|||||||
# 枚举类
|
# 枚举类
|
||||||
# ========================
|
# ========================
|
||||||
|
|
||||||
|
|
||||||
class PluginTypes(str, Enum):
|
class PluginTypes(str, Enum):
|
||||||
"""插件类型枚举"""
|
"""插件类型枚举"""
|
||||||
|
|
||||||
@@ -110,7 +113,6 @@ class PluginTypes(str, Enum):
|
|||||||
LIBRARY = "library"
|
LIBRARY = "library"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ========================
|
# ========================
|
||||||
# 数据类
|
# 数据类
|
||||||
# ========================
|
# ========================
|
||||||
@@ -131,7 +133,7 @@ class PluginConfig(ABC):
|
|||||||
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: Dict[str, Any]) -> "PluginConfig":
|
def from_dict(cls, data: Mapping[str, Any]) -> "PluginConfig":
|
||||||
"""从字典创建配置实例
|
"""从字典创建配置实例
|
||||||
|
|
||||||
参数
|
参数
|
||||||
@@ -174,7 +176,7 @@ class PluginConfig(ABC):
|
|||||||
with file_path.open("wb") as f:
|
with file_path.open("wb") as f:
|
||||||
tomli_w.dump(self.to_dict(), f, multiline_strings=False, indent=4)
|
tomli_w.dump(self.to_dict(), f, multiline_strings=False, indent=4)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise PluginConfigDumpError(e)
|
raise PluginConfigDumpError("插件配置文件无法保存。") from e
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_from_file(cls, file_path: Path) -> "PluginConfig":
|
def load_from_file(cls, file_path: Path) -> "PluginConfig":
|
||||||
@@ -199,7 +201,7 @@ class PluginConfig(ABC):
|
|||||||
with file_path.open("rb") as f:
|
with file_path.open("rb") as f:
|
||||||
return cls.from_dict(tomllib.load(f))
|
return cls.from_dict(tomllib.load(f))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise PluginConfigLoadError(e)
|
raise PluginConfigLoadError("插件配置文件无法加载。") from e
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -496,9 +498,9 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def dumpbytes(
|
def stream_dump(
|
||||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||||
) -> BinaryIO:
|
) -> Iterator[bytes]:
|
||||||
"""将完整曲目导出为对应格式的字节流
|
"""将完整曲目导出为对应格式的字节流
|
||||||
|
|
||||||
参数
|
参数
|
||||||
@@ -510,12 +512,11 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
|
|
||||||
返回
|
返回
|
||||||
====
|
====
|
||||||
BinaryIO
|
Iterator[bytes]
|
||||||
导出后的二进制字节流
|
分块导出的二进制字节串
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def dump(
|
def dump(
|
||||||
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
||||||
):
|
):
|
||||||
@@ -531,7 +532,9 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
插件配置;**可选**
|
插件配置;**可选**
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
||||||
@@ -549,9 +552,9 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def dumpbytes(
|
def stream_dump(
|
||||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||||
) -> BinaryIO:
|
) -> Iterator[bytes]:
|
||||||
"""将单个音轨导出为对应格式的字节流
|
"""将单个音轨导出为对应格式的字节流
|
||||||
|
|
||||||
参数
|
参数
|
||||||
@@ -563,12 +566,11 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
|
|
||||||
返回
|
返回
|
||||||
====
|
====
|
||||||
BinaryIO
|
Iterator[bytes]
|
||||||
导出后的二进制字节流
|
分块导出的二进制字节串
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def dump(
|
def dump(
|
||||||
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
||||||
):
|
):
|
||||||
@@ -583,7 +585,9 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
config: Optional[PluginConfig]
|
config: Optional[PluginConfig]
|
||||||
插件配置;**可选**
|
插件配置;**可选**
|
||||||
"""
|
"""
|
||||||
pass
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
class ServicePluginBase(TopPluginBase, ABC):
|
class ServicePluginBase(TopPluginBase, ABC):
|
||||||
@@ -601,15 +605,13 @@ class ServicePluginBase(TopPluginBase, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def serve(self, config: Optional[PluginConfig], *args) -> None:
|
def serve(self, config: Optional[PluginConfig]) -> None:
|
||||||
"""服务插件的运行逻辑
|
"""服务插件的运行逻辑
|
||||||
|
|
||||||
参数
|
参数
|
||||||
====
|
====
|
||||||
config: Optional[PluginConfig]
|
config: Optional[PluginConfig]
|
||||||
插件配置;**可选**
|
插件配置;**可选**
|
||||||
*args: Any
|
|
||||||
其他运行时参数
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -629,4 +631,4 @@ class LibraryPluginBase(TopPluginBase, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 怎么?
|
# 怎么?
|
||||||
# 插件的彼此依赖就不需要什么调用了吧
|
# 插件的依赖项就不需要什么调用了吧
|
||||||
|
|||||||
32
Musicreater/_utils.py
Normal file
32
Musicreater/_utils.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
音·创 v3 的功能性内容合辑
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from copy import deepcopy, copy
|
||||||
|
from typing import Any, Dict, Generator, List, Optional, Tuple, Union, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
def enumerated_stuffcopy_dictionary(
|
||||||
|
enumeration_times: int = 17, staff: T = {}
|
||||||
|
) -> Dict[int, T]:
|
||||||
|
"""
|
||||||
|
生成一个字典,其中键从 `0` 到 `enumeration_times-1`,值是 `staff` 的拷贝
|
||||||
|
"""
|
||||||
|
# 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
||||||
|
# 上面的这指的是 copy.deepcopy —— 金羿 来自 20260210
|
||||||
|
return {i: deepcopy(staff) for i in range(enumeration_times)}
|
||||||
61
Musicreater/builtin_plugins/cli_seek/__init__.py
Normal file
61
Musicreater/builtin_plugins/cli_seek/__init__.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的乐曲查看器
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from TrimMCStruct import Structure
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
MusicOperatePluginBase,
|
||||||
|
music_operate_plugin,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TerminalSeekerConfig(PluginConfig):
|
||||||
|
"""
|
||||||
|
终端查看器配置
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@music_operate_plugin("music_terminal_seeker")
|
||||||
|
class TerminalSeekerPlugin(MusicOperatePluginBase):
|
||||||
|
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="基于终端的音乐查看器",
|
||||||
|
author="金羿",
|
||||||
|
description="将乐曲信息输出到终端",
|
||||||
|
version=(0,0,1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_OPERATE,
|
||||||
|
license="Same as Musiccreater",
|
||||||
|
dependencies=(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def process(
|
||||||
|
self, data: "SingleMusic", config: TerminalSeekerConfig
|
||||||
|
) -> "SingleMusic":
|
||||||
|
...
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Minecraft 结构生成插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from .main import McstructureExportConfig, NoteDataConvert2CommandPlugin
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"McstructureExportConfig",
|
||||||
|
"NoteDataConvert2CommandPlugin",
|
||||||
|
]
|
||||||
103
Musicreater/builtin_plugins/commands_to_structure/addon.py
Normal file
103
Musicreater/builtin_plugins/commands_to_structure/addon.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Minecraft 结构生成插件中有关附加包操作的内容
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import zipfile
|
||||||
|
from typing import List, Literal, Union
|
||||||
|
|
||||||
|
|
||||||
|
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
|
||||||
|
"""
|
||||||
|
使用指定的压缩算法将目录打包为zip文件
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
sourceDir: str
|
||||||
|
要压缩的源目录路径
|
||||||
|
outFilename: str
|
||||||
|
输出的zip文件路径
|
||||||
|
compression: int, 可选
|
||||||
|
压缩算法,默认为8 (DEFLATED)
|
||||||
|
可用算法:
|
||||||
|
STORED = 0
|
||||||
|
DEFLATED = 8 (默认)
|
||||||
|
BZIP2 = 12
|
||||||
|
LZMA = 14
|
||||||
|
exceptFile: list[str], 可选
|
||||||
|
需要排除在压缩包外的文件名称列表(可选)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
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(
|
||||||
|
format_version: Union[Literal[1], Literal[2]] = 1,
|
||||||
|
pack_description: str = "",
|
||||||
|
pack_version: Union[List[int], Literal[None]] = None,
|
||||||
|
pack_name: str = "",
|
||||||
|
pack_uuid: Union[str, Literal[None]] = None,
|
||||||
|
pack_engine_version: Union[List[int], 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,
|
||||||
|
]
|
||||||
|
result = {
|
||||||
|
"format_version": format_version,
|
||||||
|
"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,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
if pack_engine_version:
|
||||||
|
result["header"]["min_engine_version"] = pack_engine_version
|
||||||
|
return result
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
存放有关BDX结构操作的内容
|
音·创 v3 内置的 Minecraft 结构生成插件中有关 BDX 结构操作的内容
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
Copyright © 2025 Eilles & bgArray
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
开源相关声明请见 仓库根目录下的 License.md
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
Terms & Conditions: License.md in the root directory
|
Terms & Conditions: License.md in the root directory
|
||||||
@@ -15,11 +15,11 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from ..constants import x, y, z
|
from Musicreater.builtin_plugins.to_commands import MineCommand
|
||||||
from ..subclass import MineCommand
|
from .common import bottem_side_length_of_smallest_square_bottom_box, x, y, z
|
||||||
from .common import bottem_side_length_of_smallest_square_bottom_box
|
|
||||||
|
|
||||||
BDX_MOVE_KEY = {
|
BDX_MOVE_KEY = {
|
||||||
"x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"],
|
"x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"],
|
||||||
@@ -161,7 +161,7 @@ def commands_to_BDX_bytes(
|
|||||||
|
|
||||||
for command in commands_list:
|
for command in commands_list:
|
||||||
_bytes += form_command_block_in_BDX_bytes(
|
_bytes += form_command_block_in_BDX_bytes(
|
||||||
command.command_text,
|
command.command,
|
||||||
(
|
(
|
||||||
(1 if y_forward else 0)
|
(1 if y_forward else 0)
|
||||||
if (
|
if (
|
||||||
@@ -181,7 +181,7 @@ def commands_to_BDX_bytes(
|
|||||||
condition=command.conditional,
|
condition=command.conditional,
|
||||||
needRedstone=False,
|
needRedstone=False,
|
||||||
tickDelay=command.delay,
|
tickDelay=command.delay,
|
||||||
customName=command.annotation_text,
|
customName=command.annotation,
|
||||||
executeOnFirstTick=False,
|
executeOnFirstTick=False,
|
||||||
trackOutput=True,
|
trackOutput=True,
|
||||||
)
|
)
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
Copyright © 2025 Eilles & bgArray
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
开源相关声明请见 仓库根目录下的 License.md
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
Terms & Conditions: License.md in the root directory
|
Terms & Conditions: License.md in the root directory
|
||||||
@@ -18,6 +18,10 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
x = "x"
|
||||||
|
y = "y"
|
||||||
|
z = "z"
|
||||||
|
|
||||||
|
|
||||||
def bottem_side_length_of_smallest_square_bottom_box(
|
def bottem_side_length_of_smallest_square_bottom_box(
|
||||||
_total_block_count: int, _max_height: int
|
_total_block_count: int, _max_height: int
|
||||||
148
Musicreater/builtin_plugins/commands_to_structure/main.py
Normal file
148
Musicreater/builtin_plugins/commands_to_structure/main.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Minecraft 结构生成插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from TrimMCStruct import Structure
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
music_output_plugin,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
track_output_plugin,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.to_commands import (
|
||||||
|
MineCommand,
|
||||||
|
NoteDataConvert2CommandPlugin,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .mcstructure import (
|
||||||
|
COMPABILITY_VERSION_117,
|
||||||
|
COMPABILITY_VERSION_119,
|
||||||
|
commands_to_structure,
|
||||||
|
commands_to_redstone_delay_structure,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class McstructureExportConfig(PluginConfig):
|
||||||
|
"""
|
||||||
|
导出 MCSTRUCTURE 结构插件的配置类
|
||||||
|
"""
|
||||||
|
|
||||||
|
music_deviation: float = 0
|
||||||
|
"""
|
||||||
|
全曲音调偏移调整,单位为 Midi Pitch
|
||||||
|
"""
|
||||||
|
minimum_volume: float = 0.01
|
||||||
|
"""
|
||||||
|
指令参数:最小音量
|
||||||
|
"""
|
||||||
|
player_selector: str = "@a"
|
||||||
|
"""
|
||||||
|
玩家选择器
|
||||||
|
"""
|
||||||
|
max_height: int = 64
|
||||||
|
"""
|
||||||
|
生成结构的最大高度
|
||||||
|
"""
|
||||||
|
enable_old_execute_format: bool = False
|
||||||
|
"""
|
||||||
|
是否使用旧版指令格式
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def execute_command_head(self) -> str:
|
||||||
|
return (
|
||||||
|
"execute {} ~ ~ ~ "
|
||||||
|
if self.enable_old_execute_format
|
||||||
|
else "execute as {} at @s positioned ~ ~ ~ run "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@music_output_plugin("music_to_mcstructure_in_delay_plugin")
|
||||||
|
class MusicExportToMcstructureWithDelayPlayerPlugin(MusicOutputPluginBase):
|
||||||
|
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="导出全曲结构插件(mcstructure结构、延迟播放器)",
|
||||||
|
author="金羿、玉衡Alioth",
|
||||||
|
description="将音·创 v3 的整首音乐数据,以指令方块延迟的播放形式,导出为基岩版 MCSTRUCTURE 结构",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_EXPORT,
|
||||||
|
license="Same as Musicreater",
|
||||||
|
dependencies=("notedata_to_command_plugin",),
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("MCSTRUCTURE",)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _go_convertion(
|
||||||
|
data: SingleMusic, config: McstructureExportConfig
|
||||||
|
) -> Tuple[Structure, Tuple[int, int, int], int]:
|
||||||
|
|
||||||
|
command_list, max_delay = (
|
||||||
|
NoteDataConvert2CommandPlugin.to_command_list_in_delay(
|
||||||
|
music=data,
|
||||||
|
music_deviation=config.music_deviation,
|
||||||
|
minimum_volume=config.minimum_volume,
|
||||||
|
player_selector=config.player_selector,
|
||||||
|
execute_command_head=config.execute_command_head,
|
||||||
|
)[:2]
|
||||||
|
)
|
||||||
|
|
||||||
|
struct, size, end_pos = commands_to_structure(
|
||||||
|
command_list,
|
||||||
|
config.max_height - 1,
|
||||||
|
compability_version_=(
|
||||||
|
COMPABILITY_VERSION_117
|
||||||
|
if config.enable_old_execute_format
|
||||||
|
else COMPABILITY_VERSION_119
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return struct, size, max_delay
|
||||||
|
|
||||||
|
def dump(self, data: SingleMusic, file_path: Path, config: McstructureExportConfig):
|
||||||
|
|
||||||
|
struct, size, max_delay = self._go_convertion(data, config)
|
||||||
|
|
||||||
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
struct.dump(f)
|
||||||
|
|
||||||
|
return size, max_delay
|
||||||
|
|
||||||
|
def stream_dump(
|
||||||
|
self, data: SingleMusic, config: McstructureExportConfig
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
struct, size, max_delay = self._go_convertion(data, config)
|
||||||
|
|
||||||
|
b_out = BytesIO()
|
||||||
|
struct.dump(b_out)
|
||||||
|
b_out.seek(0)
|
||||||
|
|
||||||
|
yield from b_out
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
存放有关MCSTRUCTURE结构操作的内容
|
音·创 v3 内置的 Minecraft 结构生成插件中有关 MCSTRUCTURE 结构操作的内容
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
Copyright © 2025 Eilles & bgArray
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
开源相关声明请见 仓库根目录下的 License.md
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
Terms & Conditions: License.md in the root directory
|
Terms & Conditions: License.md in the root directory
|
||||||
@@ -20,16 +20,22 @@ from typing import List, Literal, Tuple
|
|||||||
|
|
||||||
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
|
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
|
||||||
|
|
||||||
from ..constants import x, y, z
|
from Musicreater.builtin_plugins.to_commands import MineCommand
|
||||||
from ..subclass import MineCommand
|
from .common import bottem_side_length_of_smallest_square_bottom_box, x, y, z
|
||||||
from .common import bottem_side_length_of_smallest_square_bottom_box
|
|
||||||
|
|
||||||
|
|
||||||
def antiaxis(axis: Literal["x", "z", "X", "Z"]):
|
def antiaxis(axis: Literal["x", "z", "X", "Z"]):
|
||||||
|
"""
|
||||||
|
在 x-z 平面上,返回指定轴的另一个轴
|
||||||
|
"""
|
||||||
|
|
||||||
return z if axis == x else x
|
return z if axis == x else x
|
||||||
|
|
||||||
|
|
||||||
def forward_IER(forward: bool):
|
def forward_IER(forward: bool):
|
||||||
|
"""
|
||||||
|
把用逻辑值标记的方向值缓存正负数,用以乘上增量
|
||||||
|
"""
|
||||||
return 1 if forward else -1
|
return 1 if forward else -1
|
||||||
|
|
||||||
|
|
||||||
@@ -47,6 +53,9 @@ AXIS_PARTICULAR_VALUE = {
|
|||||||
False: 2,
|
False: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
"""
|
||||||
|
指令方块朝向对应特殊值
|
||||||
|
"""
|
||||||
|
|
||||||
# 1.19的结构兼容版本号
|
# 1.19的结构兼容版本号
|
||||||
COMPABILITY_VERSION_119: int = 17959425
|
COMPABILITY_VERSION_119: int = 17959425
|
||||||
@@ -279,12 +288,12 @@ def commands_to_structure(
|
|||||||
结构类, 结构占用大小, 终点坐标
|
结构类, 结构占用大小, 终点坐标
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
|
_side_length = bottem_side_length_of_smallest_square_bottom_box(
|
||||||
len(commands), max_height
|
len(commands), max_height
|
||||||
)
|
)
|
||||||
|
|
||||||
struct = Structure(
|
struct = Structure(
|
||||||
size=(_sideLength, max_height, _sideLength), # 声明结构大小
|
size=(_side_length, max_height, _side_length), # 声明结构大小
|
||||||
compability_version=compability_version_,
|
compability_version=compability_version_,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -300,7 +309,7 @@ def commands_to_structure(
|
|||||||
struct.set_block(
|
struct.set_block(
|
||||||
coordinate,
|
coordinate,
|
||||||
form_command_block_in_NBT_struct(
|
form_command_block_in_NBT_struct(
|
||||||
command=command.command_text,
|
command=command.command,
|
||||||
coordinate=coordinate,
|
coordinate=coordinate,
|
||||||
particularValue=(
|
particularValue=(
|
||||||
(1 if y_forward else 0)
|
(1 if y_forward else 0)
|
||||||
@@ -312,7 +321,7 @@ def commands_to_structure(
|
|||||||
(3 if z_forward else 2)
|
(3 if z_forward else 2)
|
||||||
if (
|
if (
|
||||||
((now_z != 0) and (not z_forward))
|
((now_z != 0) and (not z_forward))
|
||||||
or (z_forward and (now_z != _sideLength - 1))
|
or (z_forward and (now_z != _side_length - 1))
|
||||||
)
|
)
|
||||||
else 5
|
else 5
|
||||||
)
|
)
|
||||||
@@ -321,7 +330,7 @@ def commands_to_structure(
|
|||||||
condition=False,
|
condition=False,
|
||||||
alwaysRun=True,
|
alwaysRun=True,
|
||||||
tickDelay=command.delay,
|
tickDelay=command.delay,
|
||||||
customName=command.annotation_text,
|
customName=command.annotation,
|
||||||
executeOnFirstTick=False,
|
executeOnFirstTick=False,
|
||||||
trackOutput=True,
|
trackOutput=True,
|
||||||
compability_version_number=compability_version_,
|
compability_version_number=compability_version_,
|
||||||
@@ -337,7 +346,7 @@ def commands_to_structure(
|
|||||||
|
|
||||||
now_z += 1 if z_forward else -1
|
now_z += 1 if z_forward else -1
|
||||||
|
|
||||||
if ((now_z >= _sideLength) and z_forward) or (
|
if ((now_z >= _side_length) and z_forward) or (
|
||||||
(now_z < 0) and (not z_forward)
|
(now_z < 0) and (not z_forward)
|
||||||
):
|
):
|
||||||
now_z -= 1 if z_forward else -1
|
now_z -= 1 if z_forward else -1
|
||||||
@@ -349,7 +358,7 @@ def commands_to_structure(
|
|||||||
(
|
(
|
||||||
now_x + 1,
|
now_x + 1,
|
||||||
max_height if now_x or now_z else now_y,
|
max_height if now_x or now_z else now_y,
|
||||||
_sideLength if now_x else now_z,
|
_side_length if now_x else now_z,
|
||||||
),
|
),
|
||||||
(now_x, now_y, now_z),
|
(now_x, now_y, now_z),
|
||||||
)
|
)
|
||||||
@@ -486,7 +495,7 @@ def commands_to_redstone_delay_structure(
|
|||||||
struct.set_block(
|
struct.set_block(
|
||||||
(pos_now[x], 1, pos_now[z]),
|
(pos_now[x], 1, pos_now[z]),
|
||||||
form_command_block_in_NBT_struct(
|
form_command_block_in_NBT_struct(
|
||||||
command=cmd.command_text,
|
command=cmd.command,
|
||||||
coordinate=(pos_now[x], 1, pos_now[z]),
|
coordinate=(pos_now[x], 1, pos_now[z]),
|
||||||
particularValue=command_statevalue(extensioon_direction, forward),
|
particularValue=command_statevalue(extensioon_direction, forward),
|
||||||
# impluse= (0 if first_impluse else 2),
|
# impluse= (0 if first_impluse else 2),
|
||||||
@@ -494,7 +503,7 @@ def commands_to_redstone_delay_structure(
|
|||||||
condition=False,
|
condition=False,
|
||||||
alwaysRun=False,
|
alwaysRun=False,
|
||||||
tickDelay=cmd.delay % 2,
|
tickDelay=cmd.delay % 2,
|
||||||
customName=cmd.annotation_text,
|
customName=cmd.annotation,
|
||||||
compability_version_number=compability_version_,
|
compability_version_number=compability_version_,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -518,7 +527,7 @@ def commands_to_redstone_delay_structure(
|
|||||||
struct.set_block(
|
struct.set_block(
|
||||||
(now_pos_copy[x], 1, now_pos_copy[z]),
|
(now_pos_copy[x], 1, now_pos_copy[z]),
|
||||||
form_command_block_in_NBT_struct(
|
form_command_block_in_NBT_struct(
|
||||||
command=cmd.command_text,
|
command=cmd.command,
|
||||||
coordinate=(now_pos_copy[x], 1, now_pos_copy[z]),
|
coordinate=(now_pos_copy[x], 1, now_pos_copy[z]),
|
||||||
particularValue=command_statevalue(extensioon_direction, forward),
|
particularValue=command_statevalue(extensioon_direction, forward),
|
||||||
# impluse= (0 if first_impluse else 2),
|
# impluse= (0 if first_impluse else 2),
|
||||||
@@ -526,7 +535,7 @@ def commands_to_redstone_delay_structure(
|
|||||||
condition=False,
|
condition=False,
|
||||||
alwaysRun=False,
|
alwaysRun=False,
|
||||||
tickDelay=cmd.delay % 2,
|
tickDelay=cmd.delay % 2,
|
||||||
customName=cmd.annotation_text,
|
customName=cmd.annotation,
|
||||||
compability_version_number=compability_version_,
|
compability_version_number=compability_version_,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
音·创 v3 内置的 Midi 读取插件
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
版权所有 © 2026 金羿、玉衡Alioth
|
|
||||||
Copyright © 2026 Eilles, YuhengAlioth
|
|
||||||
|
|
||||||
开源相关声明请见 仓库根目录下的 License.md
|
|
||||||
Terms & Conditions: License.md in the root directory
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 睿乐组织 开发交流群 861684859
|
|
||||||
# Email TriM-Organization@hotmail.com
|
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
|
||||||
|
|
||||||
import mido
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from typing import BinaryIO, Optional
|
|
||||||
|
|
||||||
from Musicreater import SingleMusic
|
|
||||||
from Musicreater.plugins import (
|
|
||||||
music_input_plugin,
|
|
||||||
PluginConfig,
|
|
||||||
PluginMetaInformation,
|
|
||||||
PluginTypes,
|
|
||||||
MusicInputPluginBase,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@music_input_plugin("midi_2_music_plugin")
|
|
||||||
class MidiImport2MusicPlugin(MusicInputPluginBase):
|
|
||||||
"""Midi 音乐数据导入插件"""
|
|
||||||
|
|
||||||
metainfo = PluginMetaInformation(
|
|
||||||
name="Midi 导入插件",
|
|
||||||
author="金羿、玉衡Alioth",
|
|
||||||
description="从 Midi 文件导入音乐数据",
|
|
||||||
version=(0, 0, 1),
|
|
||||||
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
|
||||||
license="Same as Musicreater",
|
|
||||||
)
|
|
||||||
|
|
||||||
supported_formats = ("MID", "MIDI")
|
|
||||||
|
|
||||||
def loadbytes(
|
|
||||||
self, bytes_buffer_in: BinaryIO, config: PluginConfig | None
|
|
||||||
) -> SingleMusic:
|
|
||||||
midi_file = mido.MidiFile(file=bytes_buffer_in)
|
|
||||||
return SingleMusic() # =========================== TODO: 等待制作
|
|
||||||
|
|
||||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
|
|
||||||
"""从 Midi 文件导入音乐数据"""
|
|
||||||
midi_file = mido.MidiFile(filename=file_path)
|
|
||||||
return SingleMusic() # =========================== TODO: 等待制作
|
|
||||||
64
Musicreater/builtin_plugins/midi_read/__init__.py
Normal file
64
Musicreater/builtin_plugins/midi_read/__init__.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth、偷吃不是Touch
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth, Touch
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from .main import MidiImportConfig, MidiImport2MusicPlugin
|
||||||
|
|
||||||
|
|
||||||
|
from .constants import (
|
||||||
|
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||||
|
MIDI_DEFAULT_VOLUME_VALUE,
|
||||||
|
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
MM_DISLINK_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
MM_NBS_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
MM_NBS_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .utils import (
|
||||||
|
volume_2_distance_natural,
|
||||||
|
volume_2_distance_straight,
|
||||||
|
panning_2_rotation_linear,
|
||||||
|
panning_2_rotation_trigonometric,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# 插件参数和插件本体类
|
||||||
|
"MidiImportConfig",
|
||||||
|
"MidiImport2MusicPlugin",
|
||||||
|
# 内置的拟合函数
|
||||||
|
"volume_2_distance_straight",
|
||||||
|
"volume_2_distance_natural",
|
||||||
|
"panning_2_rotation_linear",
|
||||||
|
"panning_2_rotation_trigonometric",
|
||||||
|
# Midi 相关默认值
|
||||||
|
"MIDI_DEFAULT_PROGRAM_VALUE",
|
||||||
|
"MIDI_DEFAULT_VOLUME_VALUE",
|
||||||
|
# Midi 与 游戏内容 的对照表
|
||||||
|
"MM_CLASSIC_PITCHED_INSTRUMENT_TABLE",
|
||||||
|
"MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE",
|
||||||
|
"MM_TOUCH_PITCHED_INSTRUMENT_TABLE",
|
||||||
|
"MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE",
|
||||||
|
"MM_DISLINK_PITCHED_INSTRUMENT_TABLE",
|
||||||
|
"MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE",
|
||||||
|
"MM_NBS_PITCHED_INSTRUMENT_TABLE",
|
||||||
|
"MM_NBS_PERCUSSION_INSTRUMENT_TABLE",
|
||||||
|
]
|
||||||
797
Musicreater/builtin_plugins/midi_read/constants.py
Normal file
797
Musicreater/builtin_plugins/midi_read/constants.py
Normal file
@@ -0,0 +1,797 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件的数值常量
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth、偷吃不是Touch
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth, Touch
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
# Midi用对照表
|
||||||
|
|
||||||
|
MIDI_DEFAULT_VOLUME_VALUE: int = (
|
||||||
|
64 # Midi默认音量,当用户未指定时,默认使用折中默认音量
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDI_DEFAULT_PROGRAM_VALUE: int = (
|
||||||
|
74 # 当 Midi 本身与用户皆未指定音色时,默认 Flute 长笛
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Midi乐器对MC乐器对照表
|
||||||
|
|
||||||
|
# “经典”对照表,由 Chalie Ping “查理平” 和 金羿ELS 提供
|
||||||
|
|
||||||
|
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
0: "note.harp",
|
||||||
|
1: "note.harp",
|
||||||
|
2: "note.pling",
|
||||||
|
3: "note.harp",
|
||||||
|
4: "note.pling",
|
||||||
|
5: "note.pling",
|
||||||
|
6: "note.harp",
|
||||||
|
7: "note.harp",
|
||||||
|
8: "note.snare",
|
||||||
|
9: "note.harp",
|
||||||
|
10: "note.didgeridoo",
|
||||||
|
11: "note.harp",
|
||||||
|
12: "note.xylophone",
|
||||||
|
13: "note.chime",
|
||||||
|
14: "note.harp",
|
||||||
|
15: "note.harp",
|
||||||
|
16: "note.bass",
|
||||||
|
17: "note.harp",
|
||||||
|
18: "note.harp",
|
||||||
|
19: "note.harp",
|
||||||
|
20: "note.harp",
|
||||||
|
21: "note.harp",
|
||||||
|
22: "note.harp",
|
||||||
|
23: "note.guitar",
|
||||||
|
24: "note.guitar",
|
||||||
|
25: "note.guitar",
|
||||||
|
26: "note.guitar",
|
||||||
|
27: "note.guitar",
|
||||||
|
28: "note.guitar",
|
||||||
|
29: "note.guitar",
|
||||||
|
30: "note.guitar",
|
||||||
|
31: "note.bass",
|
||||||
|
32: "note.bass",
|
||||||
|
33: "note.bass",
|
||||||
|
34: "note.bass",
|
||||||
|
35: "note.bass",
|
||||||
|
36: "note.bass",
|
||||||
|
37: "note.bass",
|
||||||
|
38: "note.bass",
|
||||||
|
39: "note.bass",
|
||||||
|
40: "note.harp",
|
||||||
|
41: "note.harp",
|
||||||
|
42: "note.harp",
|
||||||
|
43: "note.harp",
|
||||||
|
44: "note.iron_xylophone",
|
||||||
|
45: "note.guitar",
|
||||||
|
46: "note.harp",
|
||||||
|
47: "note.harp",
|
||||||
|
48: "note.guitar",
|
||||||
|
49: "note.guitar",
|
||||||
|
50: "note.bit",
|
||||||
|
51: "note.bit",
|
||||||
|
52: "note.harp",
|
||||||
|
53: "note.harp",
|
||||||
|
54: "note.bit",
|
||||||
|
55: "note.flute",
|
||||||
|
56: "note.flute",
|
||||||
|
57: "note.flute",
|
||||||
|
58: "note.flute",
|
||||||
|
59: "note.flute",
|
||||||
|
60: "note.flute",
|
||||||
|
61: "note.flute",
|
||||||
|
62: "note.flute",
|
||||||
|
63: "note.flute",
|
||||||
|
64: "note.bit",
|
||||||
|
65: "note.bit",
|
||||||
|
66: "note.bit",
|
||||||
|
67: "note.bit",
|
||||||
|
68: "note.flute",
|
||||||
|
69: "note.harp",
|
||||||
|
70: "note.harp",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.flute",
|
||||||
|
74: "note.harp",
|
||||||
|
75: "note.flute",
|
||||||
|
76: "note.harp",
|
||||||
|
77: "note.harp",
|
||||||
|
78: "note.harp",
|
||||||
|
79: "note.harp",
|
||||||
|
80: "note.bit",
|
||||||
|
81: "note.bit",
|
||||||
|
82: "note.bit",
|
||||||
|
83: "note.bit",
|
||||||
|
84: "note.bit",
|
||||||
|
85: "note.bit",
|
||||||
|
86: "note.bit",
|
||||||
|
87: "note.bit",
|
||||||
|
88: "note.bit",
|
||||||
|
89: "note.bit",
|
||||||
|
90: "note.bit",
|
||||||
|
91: "note.bit",
|
||||||
|
92: "note.bit",
|
||||||
|
93: "note.bit",
|
||||||
|
94: "note.bit",
|
||||||
|
95: "note.bit",
|
||||||
|
96: "note.bit",
|
||||||
|
97: "note.bit",
|
||||||
|
98: "note.bit",
|
||||||
|
99: "note.bit",
|
||||||
|
100: "note.bit",
|
||||||
|
101: "note.bit",
|
||||||
|
102: "note.bit",
|
||||||
|
103: "note.bit",
|
||||||
|
104: "note.harp",
|
||||||
|
105: "note.banjo",
|
||||||
|
106: "note.harp",
|
||||||
|
107: "note.harp",
|
||||||
|
108: "note.harp",
|
||||||
|
109: "note.harp",
|
||||||
|
110: "note.harp",
|
||||||
|
111: "note.guitar",
|
||||||
|
112: "note.harp",
|
||||||
|
113: "note.bell",
|
||||||
|
114: "note.harp",
|
||||||
|
115: "note.cow_bell",
|
||||||
|
116: "note.bd",
|
||||||
|
117: "note.bass",
|
||||||
|
118: "note.bit",
|
||||||
|
119: "note.bd",
|
||||||
|
120: "note.guitar",
|
||||||
|
121: "note.harp",
|
||||||
|
122: "note.harp",
|
||||||
|
123: "note.harp",
|
||||||
|
124: "note.harp",
|
||||||
|
125: "note.hat",
|
||||||
|
126: "note.bd",
|
||||||
|
127: "note.snare",
|
||||||
|
}
|
||||||
|
"""“经典”乐音乐器对照表"""
|
||||||
|
|
||||||
|
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
34: "note.bd",
|
||||||
|
35: "note.bd",
|
||||||
|
36: "note.hat",
|
||||||
|
37: "note.snare",
|
||||||
|
38: "note.snare",
|
||||||
|
39: "note.snare",
|
||||||
|
40: "note.hat",
|
||||||
|
41: "note.snare",
|
||||||
|
42: "note.hat",
|
||||||
|
43: "note.snare",
|
||||||
|
44: "note.snare",
|
||||||
|
45: "note.bell",
|
||||||
|
46: "note.snare",
|
||||||
|
47: "note.snare",
|
||||||
|
48: "note.bell",
|
||||||
|
49: "note.hat",
|
||||||
|
50: "note.bell",
|
||||||
|
51: "note.bell",
|
||||||
|
52: "note.bell",
|
||||||
|
53: "note.bell",
|
||||||
|
54: "note.bell",
|
||||||
|
55: "note.bell",
|
||||||
|
56: "note.snare",
|
||||||
|
57: "note.hat",
|
||||||
|
58: "note.chime",
|
||||||
|
59: "note.iron_xylophone",
|
||||||
|
60: "note.bd",
|
||||||
|
61: "note.bd",
|
||||||
|
62: "note.xylophone",
|
||||||
|
63: "note.xylophone",
|
||||||
|
64: "note.xylophone",
|
||||||
|
65: "note.hat",
|
||||||
|
66: "note.bell",
|
||||||
|
67: "note.bell",
|
||||||
|
68: "note.hat",
|
||||||
|
69: "note.hat",
|
||||||
|
70: "note.snare",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.hat",
|
||||||
|
73: "note.hat",
|
||||||
|
74: "note.xylophone",
|
||||||
|
75: "note.hat",
|
||||||
|
76: "note.hat",
|
||||||
|
77: "note.xylophone",
|
||||||
|
78: "note.xylophone",
|
||||||
|
79: "note.bell",
|
||||||
|
80: "note.bell",
|
||||||
|
}
|
||||||
|
"""“经典”打击乐器对照表"""
|
||||||
|
|
||||||
|
# Touch “偷吃” 高准确率音色对照表
|
||||||
|
|
||||||
|
MM_TOUCH_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
0: "note.harp",
|
||||||
|
1: "note.harp",
|
||||||
|
2: "note.pling",
|
||||||
|
3: "note.harp",
|
||||||
|
4: "note.pling",
|
||||||
|
5: "note.pling",
|
||||||
|
6: "note.guitar",
|
||||||
|
7: "note.guitar",
|
||||||
|
8: "note.iron_xylophone",
|
||||||
|
9: "note.bell",
|
||||||
|
10: "note.iron_xylophone",
|
||||||
|
11: "note.iron_xylophone",
|
||||||
|
12: "note.iron_xylophone",
|
||||||
|
13: "note.xylophone",
|
||||||
|
14: "note.chime",
|
||||||
|
15: "note.banjo",
|
||||||
|
16: "note.xylophone",
|
||||||
|
17: "note.iron_xylophone",
|
||||||
|
18: "note.flute",
|
||||||
|
19: "note.flute",
|
||||||
|
20: "note.flute",
|
||||||
|
21: "note.flute",
|
||||||
|
22: "note.flute",
|
||||||
|
23: "note.flute",
|
||||||
|
24: "note.guitar",
|
||||||
|
25: "note.guitar",
|
||||||
|
26: "note.guitar",
|
||||||
|
27: "note.guitar",
|
||||||
|
28: "note.guitar",
|
||||||
|
29: "note.guitar",
|
||||||
|
30: "note.guitar",
|
||||||
|
31: "note.bass",
|
||||||
|
32: "note.bass",
|
||||||
|
33: "note.bass",
|
||||||
|
34: "note.bass",
|
||||||
|
35: "note.bass",
|
||||||
|
36: "note.bass",
|
||||||
|
37: "note.bass",
|
||||||
|
38: "note.bass",
|
||||||
|
39: "note.bass",
|
||||||
|
40: "note.flute",
|
||||||
|
41: "note.flute",
|
||||||
|
42: "note.flute",
|
||||||
|
43: "note.bass",
|
||||||
|
44: "note.flute",
|
||||||
|
45: "note.iron_xylophone",
|
||||||
|
46: "note.harp",
|
||||||
|
47: "note.snare",
|
||||||
|
48: "note.flute",
|
||||||
|
49: "note.flute",
|
||||||
|
50: "note.flute",
|
||||||
|
51: "note.flute",
|
||||||
|
52: "note.didgeridoo",
|
||||||
|
53: "note.flute",
|
||||||
|
54: "note.flute",
|
||||||
|
55: "mob.zombie.wood",
|
||||||
|
56: "note.flute",
|
||||||
|
57: "note.flute",
|
||||||
|
58: "note.flute",
|
||||||
|
59: "note.flute",
|
||||||
|
60: "note.flute",
|
||||||
|
61: "note.flute",
|
||||||
|
62: "note.flute",
|
||||||
|
63: "note.flute",
|
||||||
|
64: "note.bit",
|
||||||
|
65: "note.bit",
|
||||||
|
66: "note.bit",
|
||||||
|
67: "note.bit",
|
||||||
|
68: "note.flute",
|
||||||
|
69: "note.bit",
|
||||||
|
70: "note.banjo",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.flute",
|
||||||
|
74: "note.flute",
|
||||||
|
75: "note.flute",
|
||||||
|
76: "note.iron_xylophone",
|
||||||
|
77: "note.iron_xylophone",
|
||||||
|
78: "note.flute",
|
||||||
|
79: "note.flute",
|
||||||
|
80: "note.bit",
|
||||||
|
81: "note.bit",
|
||||||
|
82: "note.flute",
|
||||||
|
83: "note.flute",
|
||||||
|
84: "note.guitar",
|
||||||
|
85: "note.flute",
|
||||||
|
86: "note.bass",
|
||||||
|
87: "note.bass",
|
||||||
|
88: "note.bit",
|
||||||
|
89: "note.flute",
|
||||||
|
90: "note.bit",
|
||||||
|
91: "note.flute",
|
||||||
|
92: "note.bell",
|
||||||
|
93: "note.guitar",
|
||||||
|
94: "note.flute",
|
||||||
|
95: "note.bit",
|
||||||
|
96: "note.bit",
|
||||||
|
97: "note.flute",
|
||||||
|
98: "note.bell",
|
||||||
|
99: "note.bit",
|
||||||
|
100: "note.bit",
|
||||||
|
101: "note.bit",
|
||||||
|
102: "note.bit",
|
||||||
|
103: "note.bit",
|
||||||
|
104: "note.iron_xylophone",
|
||||||
|
105: "note.banjo",
|
||||||
|
106: "note.harp",
|
||||||
|
107: "note.harp",
|
||||||
|
108: "note.bell",
|
||||||
|
109: "note.flute",
|
||||||
|
110: "note.flute",
|
||||||
|
111: "note.flute",
|
||||||
|
112: "note.bell",
|
||||||
|
113: "note.xylophone",
|
||||||
|
114: "note.flute",
|
||||||
|
115: "note.hat",
|
||||||
|
116: "note.snare",
|
||||||
|
117: "note.snare",
|
||||||
|
118: "note.bd",
|
||||||
|
119: "firework.blast",
|
||||||
|
120: "note.guitar",
|
||||||
|
121: "note.harp",
|
||||||
|
122: "note.harp",
|
||||||
|
123: "note.harp",
|
||||||
|
124: "note.bit",
|
||||||
|
125: "note.hat",
|
||||||
|
126: "firework.twinkle",
|
||||||
|
127: "mob.zombie.wood",
|
||||||
|
}
|
||||||
|
"""“偷吃”乐音乐器对照表"""
|
||||||
|
|
||||||
|
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
34: "note.hat",
|
||||||
|
35: "note.bd",
|
||||||
|
36: "note.bd",
|
||||||
|
37: "note.snare",
|
||||||
|
38: "note.snare",
|
||||||
|
39: "fire.ignite",
|
||||||
|
40: "note.snare",
|
||||||
|
41: "note.hat",
|
||||||
|
42: "note.hat",
|
||||||
|
43: "firework.blast",
|
||||||
|
44: "note.hat",
|
||||||
|
45: "note.snare",
|
||||||
|
46: "note.snare",
|
||||||
|
47: "note.snare",
|
||||||
|
48: "note.bell",
|
||||||
|
49: "note.hat",
|
||||||
|
50: "note.bell",
|
||||||
|
51: "note.bell",
|
||||||
|
52: "note.bell",
|
||||||
|
53: "note.bell",
|
||||||
|
54: "note.bell",
|
||||||
|
55: "note.bell",
|
||||||
|
56: "note.snare",
|
||||||
|
57: "note.hat",
|
||||||
|
58: "note.chime",
|
||||||
|
59: "note.iron_xylophone",
|
||||||
|
60: "note.bd",
|
||||||
|
61: "note.bd",
|
||||||
|
62: "note.xylophone",
|
||||||
|
63: "note.xylophone",
|
||||||
|
64: "note.xylophone",
|
||||||
|
65: "note.hat",
|
||||||
|
66: "note.bell",
|
||||||
|
67: "note.bell",
|
||||||
|
68: "note.hat",
|
||||||
|
69: "note.hat",
|
||||||
|
70: "note.snare",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.hat",
|
||||||
|
73: "note.hat",
|
||||||
|
74: "note.xylophone",
|
||||||
|
75: "note.hat",
|
||||||
|
76: "note.hat",
|
||||||
|
77: "note.xylophone",
|
||||||
|
78: "note.xylophone",
|
||||||
|
79: "note.bell",
|
||||||
|
80: "note.bell",
|
||||||
|
}
|
||||||
|
"""“偷吃”打击乐器对照表"""
|
||||||
|
|
||||||
|
# Dislink “断联” 音色对照表
|
||||||
|
# https://github.com/Dislink/midi2bdx/blob/main/index.html
|
||||||
|
|
||||||
|
|
||||||
|
MM_DISLINK_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
0: "note.harp",
|
||||||
|
1: "note.harp",
|
||||||
|
2: "note.pling",
|
||||||
|
3: "note.harp",
|
||||||
|
4: "note.harp",
|
||||||
|
5: "note.harp",
|
||||||
|
6: "note.harp",
|
||||||
|
7: "note.harp",
|
||||||
|
8: "note.iron_xylophone",
|
||||||
|
9: "note.bell",
|
||||||
|
10: "note.iron_xylophone",
|
||||||
|
11: "note.iron_xylophone",
|
||||||
|
12: "note.iron_xylophone",
|
||||||
|
13: "note.iron_xylophone",
|
||||||
|
14: "note.chime",
|
||||||
|
15: "note.iron_xylophone",
|
||||||
|
16: "note.harp",
|
||||||
|
17: "note.harp",
|
||||||
|
18: "note.harp",
|
||||||
|
19: "note.harp",
|
||||||
|
20: "note.harp",
|
||||||
|
21: "note.harp",
|
||||||
|
22: "note.harp",
|
||||||
|
23: "note.harp",
|
||||||
|
24: "note.guitar",
|
||||||
|
25: "note.guitar",
|
||||||
|
26: "note.guitar",
|
||||||
|
27: "note.guitar",
|
||||||
|
28: "note.guitar",
|
||||||
|
29: "note.guitar",
|
||||||
|
30: "note.guitar",
|
||||||
|
31: "note.guitar",
|
||||||
|
32: "note.bass",
|
||||||
|
33: "note.bass",
|
||||||
|
34: "note.bass",
|
||||||
|
35: "note.bass",
|
||||||
|
36: "note.bass",
|
||||||
|
37: "note.bass",
|
||||||
|
38: "note.bass",
|
||||||
|
39: "note.bass",
|
||||||
|
40: "note.harp",
|
||||||
|
41: "note.flute",
|
||||||
|
42: "note.flute",
|
||||||
|
43: "note.flute",
|
||||||
|
44: "note.flute",
|
||||||
|
45: "note.harp",
|
||||||
|
46: "note.harp",
|
||||||
|
47: "note.harp",
|
||||||
|
48: "note.harp",
|
||||||
|
49: "note.harp",
|
||||||
|
50: "note.harp",
|
||||||
|
51: "note.harp",
|
||||||
|
52: "note.harp",
|
||||||
|
53: "note.harp",
|
||||||
|
54: "note.harp",
|
||||||
|
55: "note.harp",
|
||||||
|
56: "note.harp",
|
||||||
|
57: "note.harp",
|
||||||
|
58: "note.harp",
|
||||||
|
59: "note.harp",
|
||||||
|
60: "note.harp",
|
||||||
|
61: "note.harp",
|
||||||
|
62: "note.harp",
|
||||||
|
63: "note.harp",
|
||||||
|
64: "note.harp",
|
||||||
|
65: "note.harp",
|
||||||
|
66: "note.harp",
|
||||||
|
67: "note.harp",
|
||||||
|
68: "note.harp",
|
||||||
|
69: "note.harp",
|
||||||
|
70: "note.harp",
|
||||||
|
71: "note.harp",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.flute",
|
||||||
|
74: "note.flute",
|
||||||
|
75: "note.flute",
|
||||||
|
76: "note.flute",
|
||||||
|
77: "note.flute",
|
||||||
|
78: "note.flute",
|
||||||
|
79: "note.flute",
|
||||||
|
80: "note.bit",
|
||||||
|
81: "note.bit",
|
||||||
|
82: "note.harp",
|
||||||
|
83: "note.harp",
|
||||||
|
84: "note.harp",
|
||||||
|
85: "note.harp",
|
||||||
|
86: "note.harp",
|
||||||
|
87: "note.harp",
|
||||||
|
88: "note.harp",
|
||||||
|
89: "note.harp",
|
||||||
|
90: "note.harp",
|
||||||
|
91: "note.harp",
|
||||||
|
92: "note.harp",
|
||||||
|
93: "note.harp",
|
||||||
|
94: "note.harp",
|
||||||
|
95: "note.harp",
|
||||||
|
96: "note.harp",
|
||||||
|
97: "note.harp",
|
||||||
|
98: "note.harp",
|
||||||
|
99: "note.harp",
|
||||||
|
100: "note.harp",
|
||||||
|
101: "note.harp",
|
||||||
|
102: "note.harp",
|
||||||
|
103: "note.harp",
|
||||||
|
104: "note.harp",
|
||||||
|
105: "note.banjo",
|
||||||
|
106: "note.harp",
|
||||||
|
107: "note.harp",
|
||||||
|
108: "note.harp",
|
||||||
|
109: "note.harp",
|
||||||
|
110: "note.harp",
|
||||||
|
111: "note.harp",
|
||||||
|
112: "note.cow_bell",
|
||||||
|
113: "note.harp",
|
||||||
|
114: "note.harp",
|
||||||
|
115: "note.bd",
|
||||||
|
116: "note.bd",
|
||||||
|
117: "note.bd",
|
||||||
|
118: "note.bd",
|
||||||
|
119: "note.harp",
|
||||||
|
120: "note.harp",
|
||||||
|
121: "note.harp",
|
||||||
|
122: "note.harp",
|
||||||
|
123: "note.harp",
|
||||||
|
124: "note.harp",
|
||||||
|
125: "note.harp",
|
||||||
|
126: "note.harp",
|
||||||
|
127: "note.harp",
|
||||||
|
}
|
||||||
|
"""“断联”乐音乐器对照表"""
|
||||||
|
|
||||||
|
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
34: "note.bd",
|
||||||
|
35: "note.bd",
|
||||||
|
36: "note.snare",
|
||||||
|
37: "note.snare",
|
||||||
|
38: "note.bd",
|
||||||
|
39: "note.snare",
|
||||||
|
40: "note.bd",
|
||||||
|
41: "note.hat",
|
||||||
|
42: "note.bd",
|
||||||
|
43: "note.hat",
|
||||||
|
44: "note.bd",
|
||||||
|
45: "note.hat",
|
||||||
|
46: "note.bd",
|
||||||
|
47: "note.bd",
|
||||||
|
48: "note.bd",
|
||||||
|
49: "note.bd",
|
||||||
|
50: "note.bd",
|
||||||
|
51: "note.bd",
|
||||||
|
52: "note.bd",
|
||||||
|
53: "note.bd",
|
||||||
|
54: "note.bd",
|
||||||
|
55: "note.cow_bell",
|
||||||
|
56: "note.bd",
|
||||||
|
57: "note.bd",
|
||||||
|
58: "note.bd",
|
||||||
|
59: "note.bd",
|
||||||
|
60: "note.bd",
|
||||||
|
61: "note.bd",
|
||||||
|
62: "note.bd",
|
||||||
|
63: "note.bd",
|
||||||
|
64: "note.bd",
|
||||||
|
65: "note.bd",
|
||||||
|
66: "note.bd",
|
||||||
|
67: "note.bd",
|
||||||
|
68: "note.bd",
|
||||||
|
69: "note.bd",
|
||||||
|
70: "note.bd",
|
||||||
|
71: "note.bd",
|
||||||
|
72: "note.bd",
|
||||||
|
73: "note.bd",
|
||||||
|
74: "note.bd",
|
||||||
|
75: "note.bd",
|
||||||
|
76: "note.bd",
|
||||||
|
77: "note.bd",
|
||||||
|
78: "note.bd",
|
||||||
|
79: "note.bd",
|
||||||
|
80: "note.bd",
|
||||||
|
}
|
||||||
|
"""“断联”打击乐器对照表"""
|
||||||
|
|
||||||
|
# NoteBlockStudio “NBS”音色对照表
|
||||||
|
# https://github.com/OpenNBS/NoteBlockStudio/blob/main/scripts/midi_instruments/midi_instruments.gml
|
||||||
|
# 此表来自于 Commit 1ab5357c197872495197f27ad8374d711b2a5195
|
||||||
|
# 需要更新:https://github.com/OpenNBS/NoteBlockStudio/compare/main...development?diff=unified&w
|
||||||
|
|
||||||
|
MM_NBS_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
0: "note.harp",
|
||||||
|
1: "note.pling",
|
||||||
|
2: "note.harp",
|
||||||
|
3: "note.pling",
|
||||||
|
4: "note.harp",
|
||||||
|
5: "note.harp",
|
||||||
|
6: "note.guitar",
|
||||||
|
7: "note.banjo",
|
||||||
|
8: "note.bell",
|
||||||
|
9: "note.bell",
|
||||||
|
10: "note.bell",
|
||||||
|
11: "note.iron_xylophone",
|
||||||
|
12: "note.iron_xylophone",
|
||||||
|
13: "note.xylophone",
|
||||||
|
14: "note.bell",
|
||||||
|
15: "note.iron_xylophone",
|
||||||
|
16: "note.flute",
|
||||||
|
17: "note.flute",
|
||||||
|
18: "note.flute",
|
||||||
|
19: "note.flute",
|
||||||
|
20: "note.flute",
|
||||||
|
21: "note.flute",
|
||||||
|
22: "note.flute",
|
||||||
|
23: "note.flute",
|
||||||
|
24: "note.guitar",
|
||||||
|
25: "note.guitar",
|
||||||
|
26: "note.guitar",
|
||||||
|
27: "note.bass",
|
||||||
|
28: "note.guitar",
|
||||||
|
29: "note.guitar",
|
||||||
|
30: "note.bass",
|
||||||
|
31: "note.bass",
|
||||||
|
32: "note.bass",
|
||||||
|
33: "note.guitar",
|
||||||
|
34: "note.guitar",
|
||||||
|
35: "note.bass",
|
||||||
|
36: "note.pling",
|
||||||
|
37: "note.flute",
|
||||||
|
38: "note.flute",
|
||||||
|
39: "note.flute",
|
||||||
|
40: "note.flute",
|
||||||
|
41: "note.flute",
|
||||||
|
42: "note.didgeridoo",
|
||||||
|
43: "note.flute",
|
||||||
|
44: "note.didgeridoo",
|
||||||
|
45: "note.flute",
|
||||||
|
46: "note.flute",
|
||||||
|
47: "note.flute",
|
||||||
|
48: "note.flute",
|
||||||
|
49: "note.flute",
|
||||||
|
50: "note.flute",
|
||||||
|
51: "note.flute",
|
||||||
|
52: "note.flute",
|
||||||
|
53: "note.flute",
|
||||||
|
54: "note.flute",
|
||||||
|
55: "note.flute",
|
||||||
|
56: "note.flute",
|
||||||
|
57: "note.flute",
|
||||||
|
58: "note.flute",
|
||||||
|
59: "note.flute",
|
||||||
|
60: "note.bit",
|
||||||
|
61: "note.flute",
|
||||||
|
62: "note.flute",
|
||||||
|
63: "note.flute",
|
||||||
|
64: "note.flute",
|
||||||
|
65: "note.guitar",
|
||||||
|
66: "note.flute",
|
||||||
|
67: "note.flute",
|
||||||
|
68: "note.flute",
|
||||||
|
69: "note.bell",
|
||||||
|
70: "note.flute",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.flute",
|
||||||
|
74: "note.chime",
|
||||||
|
75: "note.flute",
|
||||||
|
76: "note.flute",
|
||||||
|
77: "note.guitar",
|
||||||
|
78: "note.pling",
|
||||||
|
79: "note.flute",
|
||||||
|
80: "note.guitar",
|
||||||
|
81: "note.banjo",
|
||||||
|
82: "note.banjo",
|
||||||
|
83: "note.banjo",
|
||||||
|
84: "note.guitar",
|
||||||
|
85: "note.iron_xylophone",
|
||||||
|
86: "note.flute",
|
||||||
|
87: "note.flute",
|
||||||
|
88: "note.chime",
|
||||||
|
89: "note.cow_bell",
|
||||||
|
90: "note.iron_xylophone",
|
||||||
|
91: "note.xylophone",
|
||||||
|
92: "note.basedrum",
|
||||||
|
93: "note.snare",
|
||||||
|
94: "note.snare",
|
||||||
|
95: "note.basedrum",
|
||||||
|
96: "note.snare",
|
||||||
|
97: "note.hat",
|
||||||
|
98: "note.snare",
|
||||||
|
99: "note.hat",
|
||||||
|
100: "note.basedrum",
|
||||||
|
101: "note.hat",
|
||||||
|
102: "note.basedrum",
|
||||||
|
103: "note.hat",
|
||||||
|
104: "note.basedrum",
|
||||||
|
105: "note.snare",
|
||||||
|
106: "note.snare",
|
||||||
|
107: "note.snare",
|
||||||
|
108: "note.cow_bell",
|
||||||
|
109: "note.snare",
|
||||||
|
110: "note.hat",
|
||||||
|
111: "note.snare",
|
||||||
|
112: "note.hat",
|
||||||
|
113: "note.hat",
|
||||||
|
114: "note.hat",
|
||||||
|
115: "note.hat",
|
||||||
|
116: "note.hat",
|
||||||
|
117: "note.chime",
|
||||||
|
118: "note.hat",
|
||||||
|
119: "note.snare",
|
||||||
|
120: "note.hat",
|
||||||
|
121: "note.hat",
|
||||||
|
122: "note.hat",
|
||||||
|
123: "note.hat",
|
||||||
|
124: "note.hat",
|
||||||
|
125: "note.snare",
|
||||||
|
126: "note.basedrum",
|
||||||
|
127: "note.basedrum",
|
||||||
|
}
|
||||||
|
"""“NBS”乐音乐器对照表"""
|
||||||
|
|
||||||
|
|
||||||
|
MM_NBS_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
24: "note.bit",
|
||||||
|
25: "note.snare",
|
||||||
|
26: "note.hat",
|
||||||
|
27: "note.snare",
|
||||||
|
28: "note.snare",
|
||||||
|
29: "note.hat",
|
||||||
|
30: "note.hat",
|
||||||
|
31: "note.hat",
|
||||||
|
32: "note.hat",
|
||||||
|
33: "note.hat",
|
||||||
|
34: "note.chime",
|
||||||
|
35: "note.basedrum",
|
||||||
|
36: "note.basedrum",
|
||||||
|
37: "note.hat",
|
||||||
|
38: "note.snare",
|
||||||
|
39: "note.hat",
|
||||||
|
40: "note.snare",
|
||||||
|
41: "note.basedrum",
|
||||||
|
42: "note.snare",
|
||||||
|
43: "note.basedrum",
|
||||||
|
44: "note.snare",
|
||||||
|
45: "note.basedrum",
|
||||||
|
46: "note.basedrum",
|
||||||
|
47: "note.snare",
|
||||||
|
48: "note.snare",
|
||||||
|
49: "note.snare",
|
||||||
|
50: "note.snare",
|
||||||
|
51: "note.snare",
|
||||||
|
52: "note.snare",
|
||||||
|
53: "note.hat",
|
||||||
|
54: "note.snare",
|
||||||
|
55: "note.snare",
|
||||||
|
56: "note.cow_bell",
|
||||||
|
57: "note.snare",
|
||||||
|
58: "note.hat",
|
||||||
|
59: "note.snare",
|
||||||
|
60: "note.hat",
|
||||||
|
61: "note.hat",
|
||||||
|
62: "note.hat",
|
||||||
|
63: "note.basedrum",
|
||||||
|
64: "note.basedrum",
|
||||||
|
65: "note.snare",
|
||||||
|
66: "note.snare",
|
||||||
|
67: "note.xylophone",
|
||||||
|
68: "note.xylophone",
|
||||||
|
69: "note.hat",
|
||||||
|
70: "note.hat",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.hat",
|
||||||
|
74: "note.hat",
|
||||||
|
75: "note.hat",
|
||||||
|
76: "note.hat",
|
||||||
|
77: "note.hat",
|
||||||
|
78: "note.didgeridoo",
|
||||||
|
79: "note.didgeridoo",
|
||||||
|
80: "note.hat",
|
||||||
|
81: "note.chime",
|
||||||
|
82: "note.hat",
|
||||||
|
83: "note.chime",
|
||||||
|
84: "note.chime",
|
||||||
|
85: "note.hat",
|
||||||
|
86: "note.basedrum",
|
||||||
|
87: "note.basedrum",
|
||||||
|
}
|
||||||
|
"""“NBS”打击乐器对照表"""
|
||||||
69
Musicreater/builtin_plugins/midi_read/exceptions.py
Normal file
69
Musicreater/builtin_plugins/midi_read/exceptions.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件用到的一些报错类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles & YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from Musicreater.exceptions import MusicreaterOuterlyError
|
||||||
|
|
||||||
|
|
||||||
|
class MidiFormatError(MusicreaterOuterlyError):
|
||||||
|
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||||
|
super().__init__("MIDI 格式错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NotDefineTempoError(MidiFormatError):
|
||||||
|
"""没有Tempo设定导致时间无法计算的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""没有Tempo设定导致时间无法计算的错误"""
|
||||||
|
super().__init__("在曲目开始时没有声明 Tempo(未指定拍长):", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelOverFlowError(MidiFormatError):
|
||||||
|
"""一个midi中含有过多的通道"""
|
||||||
|
|
||||||
|
def __init__(self, max_channel=16, *args):
|
||||||
|
"""一个midi中含有过多的通道"""
|
||||||
|
super().__init__("含有过多的通道(数量应≤{}):".format(max_channel), *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NotDefineProgramError(MidiFormatError):
|
||||||
|
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||||
|
super().__init__("未指定演奏乐器:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NoteOnOffMismatchError(MidiFormatError):
|
||||||
|
"""音符开音和停止不匹配的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音符开音和停止不匹配的错误"""
|
||||||
|
super().__init__("音符不匹配:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class LyricMismatchError(MidiFormatError):
|
||||||
|
"""歌词匹配解析错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""有可能产生了错误的歌词解析"""
|
||||||
|
super().__init__("歌词解析错误:", *args)
|
||||||
493
Musicreater/builtin_plugins/midi_read/main.py
Normal file
493
Musicreater/builtin_plugins/midi_read/main.py
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth、偷吃不是Touch
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth, Touch
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
import mido
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
music_input_plugin,
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
MusicInputPluginBase,
|
||||||
|
)
|
||||||
|
from Musicreater.exceptions import ZeroSpeedError, IllegalMinimumVolumeError
|
||||||
|
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||||
|
|
||||||
|
from .constants import (
|
||||||
|
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||||
|
MIDI_DEFAULT_VOLUME_VALUE,
|
||||||
|
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
)
|
||||||
|
from .exceptions import (
|
||||||
|
NoteOnOffMismatchError,
|
||||||
|
ChannelOverFlowError,
|
||||||
|
LyricMismatchError,
|
||||||
|
)
|
||||||
|
from .utils import (
|
||||||
|
volume_2_distance_natural,
|
||||||
|
panning_2_rotation_trigonometric,
|
||||||
|
midi_msgs_to_noteinfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MidiImportConfig(PluginConfig):
|
||||||
|
"""Midi 音乐数据导入插件配置"""
|
||||||
|
|
||||||
|
# 系统设置
|
||||||
|
ignore_errors: bool = True
|
||||||
|
|
||||||
|
# 处理设置
|
||||||
|
speed_multiplier: float = 1.0
|
||||||
|
|
||||||
|
# 兼容不良 Midi 所定义的默认值
|
||||||
|
default_program_value: int = MIDI_DEFAULT_PROGRAM_VALUE
|
||||||
|
default_volume_value: int = MIDI_DEFAULT_VOLUME_VALUE
|
||||||
|
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||||
|
|
||||||
|
# 对照表,此处 None 值在下边 post init 函数中有处理
|
||||||
|
pitched_note_reference_table: Mapping[int, str] = None # type: ignore
|
||||||
|
percussion_note_reference_table: Mapping[int, str] = None # type: ignore
|
||||||
|
note_replacement_table: Mapping[str, str] = None # type: ignore
|
||||||
|
|
||||||
|
# 参数转换函数
|
||||||
|
volume_process_function: Callable[[float], float] = volume_2_distance_natural
|
||||||
|
panning_processing_function: Callable[[float], float] = (
|
||||||
|
panning_2_rotation_trigonometric
|
||||||
|
)
|
||||||
|
|
||||||
|
# 分轨方式
|
||||||
|
divide_tracks_by_miditrack: bool = True
|
||||||
|
divide_tracks_by_midichannel: bool = False
|
||||||
|
divide_tracks_by_soundname: bool = True
|
||||||
|
divide_tracks_by_volume: bool = False
|
||||||
|
divide_tracks_by_panning: bool = False
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.pitched_note_reference_table = (
|
||||||
|
self.pitched_note_reference_table
|
||||||
|
if self.pitched_note_reference_table
|
||||||
|
else MM_TOUCH_PITCHED_INSTRUMENT_TABLE
|
||||||
|
)
|
||||||
|
self.percussion_note_reference_table = (
|
||||||
|
self.percussion_note_reference_table
|
||||||
|
if self.percussion_note_reference_table
|
||||||
|
else MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE
|
||||||
|
)
|
||||||
|
self.note_replacement_table = (
|
||||||
|
self.note_replacement_table if self.note_replacement_table else {}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ControlerKeys(Enum):
|
||||||
|
"""
|
||||||
|
Midi 控制器键
|
||||||
|
"""
|
||||||
|
|
||||||
|
MIDI_PROGRAM = "midi_program"
|
||||||
|
MIDI_VOLUME = "midi_volume"
|
||||||
|
MIDI_PAN = "midi_pan"
|
||||||
|
|
||||||
|
|
||||||
|
class TrackDivisionDict(
|
||||||
|
Dict[
|
||||||
|
Tuple[
|
||||||
|
Optional[int],
|
||||||
|
Optional[int],
|
||||||
|
Optional[str],
|
||||||
|
Optional[float],
|
||||||
|
Optional[Tuple[float, float]],
|
||||||
|
],
|
||||||
|
SingleTrack,
|
||||||
|
]
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
音轨分轨字典
|
||||||
|
键为音轨信息元组[音轨编号, 通道编号, 乐器名称, 音量, 声相]
|
||||||
|
值为音轨对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
division_by_miditrack: bool = True
|
||||||
|
division_by_midichannel: bool = False
|
||||||
|
division_by_soundname: bool = True
|
||||||
|
division_by_volume: bool = False
|
||||||
|
division_by_panning: bool = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args,
|
||||||
|
midi_import_config: MidiImportConfig = MidiImportConfig(),
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.division_by_miditrack = midi_import_config.divide_tracks_by_miditrack
|
||||||
|
self.division_by_midichannel = midi_import_config.divide_tracks_by_midichannel
|
||||||
|
self.division_by_soundname = midi_import_config.divide_tracks_by_soundname
|
||||||
|
self.division_by_volume = midi_import_config.divide_tracks_by_volume
|
||||||
|
self.division_by_panning = midi_import_config.divide_tracks_by_panning
|
||||||
|
|
||||||
|
def __getitem__(
|
||||||
|
self,
|
||||||
|
key: Tuple[
|
||||||
|
Optional[int],
|
||||||
|
Optional[int],
|
||||||
|
Optional[str],
|
||||||
|
Optional[float],
|
||||||
|
Optional[Tuple[float, float]],
|
||||||
|
],
|
||||||
|
) -> SingleTrack:
|
||||||
|
key = (
|
||||||
|
key[0] if self.division_by_miditrack else None,
|
||||||
|
key[1] if self.division_by_midichannel else None,
|
||||||
|
key[2] if self.division_by_soundname else None,
|
||||||
|
key[3] if self.division_by_volume else None,
|
||||||
|
key[4] if self.division_by_panning else None,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return super().__getitem__(key)
|
||||||
|
except KeyError:
|
||||||
|
self[key] = SingleTrack()
|
||||||
|
return self[key]
|
||||||
|
|
||||||
|
|
||||||
|
@music_input_plugin("midi_to_music_plugin")
|
||||||
|
class MidiImport2MusicPlugin(MusicInputPluginBase):
|
||||||
|
"""Midi 音乐数据导入插件"""
|
||||||
|
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="Midi 导入插件",
|
||||||
|
author="金羿、玉衡Alioth",
|
||||||
|
description="从 Midi 文件导入音乐数据",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
||||||
|
license="Same as Musicreater",
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("MID", "MIDI")
|
||||||
|
|
||||||
|
def loadbytes(
|
||||||
|
self,
|
||||||
|
bytes_buffer_in: BinaryIO,
|
||||||
|
config: Optional[MidiImportConfig] = MidiImportConfig(),
|
||||||
|
) -> SingleMusic:
|
||||||
|
return self.midifile_2_singlemusic(
|
||||||
|
mido.MidiFile(file=bytes_buffer_in, clip=True),
|
||||||
|
config if config else MidiImportConfig(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def load(
|
||||||
|
self, file_path: Path, config: Optional[MidiImportConfig] = MidiImportConfig()
|
||||||
|
) -> SingleMusic:
|
||||||
|
"""从 Midi 文件导入音乐数据"""
|
||||||
|
return self.midifile_2_singlemusic(
|
||||||
|
mido.MidiFile(filename=file_path, clip=True),
|
||||||
|
config if config else MidiImportConfig(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def midifile_2_singlemusic(
|
||||||
|
midi: mido.MidiFile,
|
||||||
|
config: MidiImportConfig = MidiImportConfig(),
|
||||||
|
) -> SingleMusic:
|
||||||
|
"""
|
||||||
|
将midi解析并转换为频道音符字典
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
midi: mido.MidiFile 对象
|
||||||
|
需要处理的midi对象
|
||||||
|
speed: float
|
||||||
|
音乐播放速度倍数
|
||||||
|
default_program_value: int
|
||||||
|
默认的 MIDI 乐器值
|
||||||
|
default_volume_value: int
|
||||||
|
默认的通道音量值
|
||||||
|
default_tempo_value: int
|
||||||
|
默认的 MIDI TEMPO 值
|
||||||
|
pitched_note_rtable: Dict[int, Tuple[str, int]]
|
||||||
|
乐音乐器Midi-MC对照表
|
||||||
|
percussion_note_rtable: Dict[int, Tuple[str, int]]
|
||||||
|
打击乐器Midi-MC对照表
|
||||||
|
vol_processing_function: Callable[[float], float]
|
||||||
|
音量对播放距离的拟合函数
|
||||||
|
pan_processing_function: Callable[[float], float]
|
||||||
|
声像偏移对播放旋转角度的拟合函数
|
||||||
|
note_rtable_replacement: Dict[str, str]
|
||||||
|
音符名称替换表,此表用于对 Minecraft 乐器名称进行替换,而非 Midi Program 的替换
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[SingleMusic, int, Dict[str, int]]
|
||||||
|
以通道作为分割的Midi音符列表字典, 音符总数, 乐器使用统计
|
||||||
|
"""
|
||||||
|
|
||||||
|
if config.speed_multiplier == 0:
|
||||||
|
raise ZeroSpeedError("播放速度不得为零,应为 (0,1] 范围内的实数。")
|
||||||
|
|
||||||
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
|
divided_tracks: TrackDivisionDict = TrackDivisionDict(midi_import_config=config)
|
||||||
|
|
||||||
|
value_controler_per_channel: Dict[int, Dict[ControlerKeys, int]] = (
|
||||||
|
enumerated_stuffcopy_dictionary(
|
||||||
|
staff={
|
||||||
|
ControlerKeys.MIDI_PROGRAM: config.default_program_value,
|
||||||
|
ControlerKeys.MIDI_VOLUME: config.default_volume_value,
|
||||||
|
ControlerKeys.MIDI_PAN: 64,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
midi_tempo = config.default_tempo_value
|
||||||
|
"""微秒每拍"""
|
||||||
|
note_count = 0
|
||||||
|
"""音符计数"""
|
||||||
|
note_count_per_instrument: Dict[str, int] = {}
|
||||||
|
"""乐器使用统计"""
|
||||||
|
|
||||||
|
note_queue_A: Dict[int, List[Tuple[int, int]]] = (
|
||||||
|
enumerated_stuffcopy_dictionary(staff=[])
|
||||||
|
)
|
||||||
|
"""音符队列甲 Dict[通道, List[Tuple[int音高, int轨道]]]"""
|
||||||
|
note_queue_B: Dict[int, List[Tuple[int, int, int, int, int]]] = (
|
||||||
|
enumerated_stuffcopy_dictionary(staff=[])
|
||||||
|
)
|
||||||
|
"""音符队列乙 Dict[通道, List[Tuple[int力度, int乐器, int音量, int偏移, int微秒时间]]]"""
|
||||||
|
|
||||||
|
midi_lyric_cache: List[Tuple[int, str]] = []
|
||||||
|
"""歌词缓存 List[Tuple[int微秒时间, str歌词内容]]"""
|
||||||
|
|
||||||
|
midi_text_list: List[str] = []
|
||||||
|
"""Midi 附加文本列表"""
|
||||||
|
midi_copyright_list: List[str] = []
|
||||||
|
"""Midi 版权列表"""
|
||||||
|
midi_track_name_dict: Dict[int, str] = {}
|
||||||
|
"""轨道名称字典 Dict[int轨道编号, str轨道名称]"""
|
||||||
|
|
||||||
|
for track_no, message_track in enumerate(midi.tracks):
|
||||||
|
# 每个音轨单独重置
|
||||||
|
|
||||||
|
microseconds = 0
|
||||||
|
"""当前的微妙时间"""
|
||||||
|
for msg in message_track:
|
||||||
|
if msg.type == "set_tempo":
|
||||||
|
# Tempo 改变是一个全局的控制
|
||||||
|
# 而且应该是很早出现的一个 Midi 消息
|
||||||
|
midi_tempo = msg.tempo
|
||||||
|
|
||||||
|
if msg.time != 0:
|
||||||
|
# 微秒
|
||||||
|
# 通常情况下,tempo 是 500000,tpb 在
|
||||||
|
microseconds += msg.time * midi_tempo / midi.ticks_per_beat
|
||||||
|
|
||||||
|
if msg.type == "program_change":
|
||||||
|
# 检测 乐器变化 之 midi 事件
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PROGRAM
|
||||||
|
] = msg.program
|
||||||
|
|
||||||
|
elif msg.is_cc(7):
|
||||||
|
# Control Change 更改当前通道的 音量 的事件(大幅度,最高有效位)
|
||||||
|
# print("通道、轨道、音量修改:",msg.channel, track_no, msg.value)
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_VOLUME
|
||||||
|
] = msg.value
|
||||||
|
elif msg.is_cc(10):
|
||||||
|
# Control Change 更改当前通道的 音调偏移 的事件(大幅度,最高有效位)
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PAN
|
||||||
|
] = msg.value
|
||||||
|
|
||||||
|
elif msg.type == "lyrics":
|
||||||
|
# 歌词事件
|
||||||
|
midi_lyric_cache.append((microseconds, msg.text))
|
||||||
|
# print(lyric_cache, flush=True)
|
||||||
|
elif msg.type == "text":
|
||||||
|
# 检测文本事件
|
||||||
|
midi_text_list.append(msg.text)
|
||||||
|
elif msg.type == "copyright":
|
||||||
|
# 检测版权事件
|
||||||
|
midi_copyright_list.append(msg.text)
|
||||||
|
elif msg.type == "track_name":
|
||||||
|
# 检测轨道名称事件
|
||||||
|
midi_track_name_dict[track_no] = msg.name
|
||||||
|
elif msg.type == "note_on" and msg.velocity != 0:
|
||||||
|
# 一个音符开始弹奏
|
||||||
|
|
||||||
|
# 加入音符队列甲(按通道分隔)
|
||||||
|
# (音高, 轨道)
|
||||||
|
note_queue_A[msg.channel].append((msg.note, track_no))
|
||||||
|
# 音符队列乙(按通道分隔)
|
||||||
|
# (力度, 乐器, 音量, 偏移, 微秒)
|
||||||
|
note_queue_B[msg.channel].append(
|
||||||
|
(
|
||||||
|
msg.velocity,
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PROGRAM
|
||||||
|
],
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_VOLUME
|
||||||
|
],
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PAN
|
||||||
|
],
|
||||||
|
microseconds,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif (msg.type == "note_off") or (
|
||||||
|
msg.type == "note_on" and msg.velocity == 0
|
||||||
|
):
|
||||||
|
# 一个音符结束弹奏
|
||||||
|
|
||||||
|
if (
|
||||||
|
msg.note,
|
||||||
|
track_no,
|
||||||
|
) in note_queue_A[msg.channel]:
|
||||||
|
# 在甲队列中发现了同一个 音高和乐器且在同轨道 的音符
|
||||||
|
|
||||||
|
# 获取其音符力度和微秒数
|
||||||
|
_velocity, _program, _volume, _panning, _start_ms = (
|
||||||
|
note_queue_B[msg.channel][
|
||||||
|
note_queue_A[msg.channel].index((msg.note, track_no))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 在队列中删除此音符
|
||||||
|
note_queue_A[msg.channel].remove((msg.note, track_no))
|
||||||
|
note_queue_B[msg.channel].remove(
|
||||||
|
(_velocity, _program, _volume, _panning, _start_ms)
|
||||||
|
)
|
||||||
|
|
||||||
|
_lyric = ""
|
||||||
|
# 找一找歌词吧
|
||||||
|
if midi_lyric_cache:
|
||||||
|
for i in range(len(midi_lyric_cache)):
|
||||||
|
if midi_lyric_cache[i][0] >= _start_ms:
|
||||||
|
_lyric = midi_lyric_cache.pop(i)[1]
|
||||||
|
break
|
||||||
|
|
||||||
|
# 更新结果信息
|
||||||
|
|
||||||
|
that_note, sound_name, orign_distance, sound_rotation = (
|
||||||
|
midi_msgs_to_noteinfo(
|
||||||
|
inst=(
|
||||||
|
msg.note
|
||||||
|
if (_is_percussion := (msg.channel == 9))
|
||||||
|
else _program
|
||||||
|
),
|
||||||
|
note=(_program if _is_percussion else msg.note),
|
||||||
|
percussive=_is_percussion,
|
||||||
|
volume=_volume,
|
||||||
|
velocity=_velocity,
|
||||||
|
panning=_panning,
|
||||||
|
start_time=_start_ms, # 微秒
|
||||||
|
duration=microseconds - _start_ms, # 微秒
|
||||||
|
play_speed=config.speed_multiplier,
|
||||||
|
midi_reference_table=(
|
||||||
|
config.percussion_note_reference_table
|
||||||
|
if _is_percussion
|
||||||
|
else config.pitched_note_reference_table
|
||||||
|
),
|
||||||
|
volume_processing_method=config.volume_process_function,
|
||||||
|
panning_processing_method=config.panning_processing_function,
|
||||||
|
note_table_replacement=config.note_replacement_table,
|
||||||
|
lyric_line=_lyric,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# print(that_note.start_time, end=", ")
|
||||||
|
|
||||||
|
divided_tracks[
|
||||||
|
(
|
||||||
|
track_no,
|
||||||
|
msg.channel,
|
||||||
|
sound_name,
|
||||||
|
orign_distance,
|
||||||
|
sound_rotation,
|
||||||
|
)
|
||||||
|
].add(that_note)
|
||||||
|
|
||||||
|
# 更新统计信息
|
||||||
|
note_count += 1
|
||||||
|
if sound_name in note_count_per_instrument.keys():
|
||||||
|
note_count_per_instrument[sound_name] += 1
|
||||||
|
else:
|
||||||
|
note_count_per_instrument[sound_name] = 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 什么?找不到 note on 消息??
|
||||||
|
if config.ignore_errors:
|
||||||
|
print(
|
||||||
|
"[WARRING] MIDI格式错误 音符不匹配`{}`无法在上文`{}`中找到与之匹配的音符开音消息".format(
|
||||||
|
msg, note_queue_A[msg.channel]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise NoteOnOffMismatchError(
|
||||||
|
"当前的MIDI很可能有损坏之嫌……",
|
||||||
|
msg,
|
||||||
|
"无法在上文中找到与之匹配的音符开音消息。",
|
||||||
|
)
|
||||||
|
|
||||||
|
del midi_tempo
|
||||||
|
|
||||||
|
if midi_lyric_cache:
|
||||||
|
# 怎么有歌词多啊
|
||||||
|
if config.ignore_errors:
|
||||||
|
print(
|
||||||
|
"[WARRING] MIDI 解析错误 歌词对应错误,以下歌词未能填入音符之中,已经填入的仍可能有误 {}".format(
|
||||||
|
midi_lyric_cache
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise LyricMismatchError(
|
||||||
|
"MIDI 解析产生错误",
|
||||||
|
"歌词解析过程中无法对应音符,已填入的音符仍可能有误",
|
||||||
|
midi_lyric_cache,
|
||||||
|
)
|
||||||
|
|
||||||
|
final_music = SingleMusic(
|
||||||
|
credits="; ".join(midi_copyright_list),
|
||||||
|
extra_information={
|
||||||
|
"MIDI_TEXT_LIST": midi_text_list,
|
||||||
|
"NOTE_COUNT": note_count,
|
||||||
|
"NOTE_COUNT_PER_INSTRUMENT": note_count_per_instrument,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for track_properties, every_single_track in divided_tracks.items():
|
||||||
|
# [音轨编号, 通道编号, 乐器名称, 音量, 声相]
|
||||||
|
if track_properties[0] and (
|
||||||
|
track_name := midi_track_name_dict.get(track_properties[0])
|
||||||
|
): # 音轨编号
|
||||||
|
every_single_track.name = track_name
|
||||||
|
if track_properties[2]: # 乐器名称
|
||||||
|
every_single_track.instrument = track_properties[2]
|
||||||
|
if track_properties[3]: # 音量
|
||||||
|
every_single_track.sound_position.sound_distance = track_properties[3]
|
||||||
|
if track_properties[4]: # 声相
|
||||||
|
every_single_track.sound_position.sound_azimuth = track_properties[4]
|
||||||
|
final_music.append(every_single_track)
|
||||||
|
|
||||||
|
return final_music
|
||||||
222
Musicreater/builtin_plugins/midi_read/utils.py
Normal file
222
Musicreater/builtin_plugins/midi_read/utils.py
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件的功能方法
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Mapping
|
||||||
|
|
||||||
|
from Musicreater import SingleNote, SoundAtmos
|
||||||
|
|
||||||
|
|
||||||
|
def volume_2_distance_natural(
|
||||||
|
vol: float,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
Midi 力度值/音量值拟合成的距离函数,一种更加自然的听感?
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
vol: int
|
||||||
|
Midi 音符力度值(0~127)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float播放中心到玩家的距离
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
-8.081720684086314
|
||||||
|
* math.log(
|
||||||
|
vol + 14.579508825070013,
|
||||||
|
)
|
||||||
|
+ 37.65806375944386
|
||||||
|
if vol < 60.64
|
||||||
|
else 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649)
|
||||||
|
+ -6.313841334963396 * (vol + 2592.272889454798)
|
||||||
|
+ 4558.496367823575
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def volume_2_distance_straight(vol: float) -> float:
|
||||||
|
"""
|
||||||
|
Midi 力度值/音量值拟合成的距离函数,线性转换
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
vol: int
|
||||||
|
Midi 音符力度值(0~127)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float播放中心到玩家的距离
|
||||||
|
"""
|
||||||
|
return (vol + 1) / -8 + 16
|
||||||
|
|
||||||
|
|
||||||
|
def panning_2_rotation_linear(pan_: float) -> float:
|
||||||
|
"""
|
||||||
|
Midi 左右平衡偏移值线性转为声源旋转角度
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
pan_: int
|
||||||
|
Midi 左右平衡偏移值
|
||||||
|
注:此参数为int,范围从 0 到 127,当为 64 时,声源居中
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
声源旋转角度
|
||||||
|
"""
|
||||||
|
return (pan_ - 64) * 90 / 63
|
||||||
|
|
||||||
|
|
||||||
|
def panning_2_rotation_trigonometric(pan_: float) -> float:
|
||||||
|
"""
|
||||||
|
Midi 左右平衡偏移值,依照圆的声场定位,转为声源旋转角度
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
pan_: int
|
||||||
|
Midi 左右平衡偏移值
|
||||||
|
注:此参数为int,范围从 0 到 127,当为 64 时,声源居中
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
声源旋转角度
|
||||||
|
"""
|
||||||
|
if pan_ <= 0:
|
||||||
|
return -90
|
||||||
|
elif pan_ >= 127:
|
||||||
|
return 90
|
||||||
|
else:
|
||||||
|
return math.degrees(math.acos((64 - pan_) / 63)) - 90
|
||||||
|
|
||||||
|
|
||||||
|
def midi_inst_to_mc_sound(
|
||||||
|
instrumentID: int,
|
||||||
|
reference_table: Mapping[int, str],
|
||||||
|
default_instrument: str = "note.flute",
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
返回midi的乐器ID对应的我的世界乐器名
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
instrumentID: int
|
||||||
|
midi的乐器ID
|
||||||
|
reference_table: Dict[int, Tuple[str, int]]
|
||||||
|
转换乐器参照表
|
||||||
|
default_instrument: str
|
||||||
|
查无此乐器时的替换乐器
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str我的世界乐器名
|
||||||
|
"""
|
||||||
|
return reference_table.get(
|
||||||
|
instrumentID,
|
||||||
|
default_instrument,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def midi_msgs_to_noteinfo(
|
||||||
|
inst: int, # 乐器编号
|
||||||
|
note: int,
|
||||||
|
percussive: bool, # 是否作为打击乐器启用
|
||||||
|
volume: int,
|
||||||
|
velocity: int,
|
||||||
|
panning: int,
|
||||||
|
start_time: int,
|
||||||
|
duration: int,
|
||||||
|
play_speed: float,
|
||||||
|
midi_reference_table: Mapping[int, str],
|
||||||
|
volume_processing_method: Callable[[float], float],
|
||||||
|
panning_processing_method: Callable[[float], float],
|
||||||
|
note_table_replacement: Mapping[str, str] = {},
|
||||||
|
lyric_line: str = "",
|
||||||
|
) -> Tuple[SingleNote, str, float, Tuple[float, float]]:
|
||||||
|
"""
|
||||||
|
将 Midi信息转为音符对象
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
inst: int
|
||||||
|
乐器编号
|
||||||
|
note: int
|
||||||
|
音高编号(音符编号)
|
||||||
|
percussive: bool
|
||||||
|
是否作为打击乐器启用
|
||||||
|
volume: int
|
||||||
|
音量
|
||||||
|
velocity: int
|
||||||
|
力度
|
||||||
|
panning: int
|
||||||
|
声相偏移
|
||||||
|
start_time: int
|
||||||
|
音符起始时间(微秒)
|
||||||
|
duration: int
|
||||||
|
音符持续时间(微秒)
|
||||||
|
play_speed: float
|
||||||
|
曲目播放速度
|
||||||
|
midi_reference_table: Dict[int, str]
|
||||||
|
转换对照表
|
||||||
|
volume_processing_method: Callable[[float], float]
|
||||||
|
音量处理函数
|
||||||
|
panning_processing_method: Callable[[float], float]
|
||||||
|
立体声相偏移处理函数
|
||||||
|
note_table_replacement: Dict[str, str]
|
||||||
|
音符替换表,定义 Minecraft 音符字串的替换
|
||||||
|
lyric_line: str
|
||||||
|
该音符的歌词
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
Tuple[
|
||||||
|
MineNote我的世界音符对象,
|
||||||
|
str我的世界声音名,
|
||||||
|
float播放中心到玩家的距离,
|
||||||
|
Tuple[float, float]声源旋转角度
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
mc_sound_ID = midi_inst_to_mc_sound(
|
||||||
|
inst,
|
||||||
|
midi_reference_table,
|
||||||
|
"note.bd" if percussive else "note.flute",
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
SingleNote(
|
||||||
|
note_pitch=note,
|
||||||
|
note_volume=velocity,
|
||||||
|
start_tick=(tk := int(start_time / float(play_speed) / 50000)),
|
||||||
|
keep_tick=round(duration / float(play_speed) / 50000),
|
||||||
|
mass_precision_time=round(
|
||||||
|
(start_time / float(play_speed) - tk * 50000) / 800
|
||||||
|
),
|
||||||
|
extra_information={
|
||||||
|
"LYRIC_TEXT": lyric_line,
|
||||||
|
"VOLUME_VALUE": volume,
|
||||||
|
"PIN_VALUE": panning,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
note_table_replacement.get(mc_sound_ID, mc_sound_ID),
|
||||||
|
volume_processing_method(volume),
|
||||||
|
(panning_processing_method(panning), 0),
|
||||||
|
)
|
||||||
33
Musicreater/builtin_plugins/to_commands/__init__.py
Normal file
33
Musicreater/builtin_plugins/to_commands/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 指令生成插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from .main import NoteDataConvert2CommandPlugin, MineCommand
|
||||||
|
|
||||||
|
from .progressbar import ProgressBarStyle, DEFAULT_PROGRESSBAR_STYLE
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# "CommandConvertionConfig",
|
||||||
|
# 插件主类
|
||||||
|
"NoteDataConvert2CommandPlugin",
|
||||||
|
# 进度条样式类
|
||||||
|
"ProgressBarStyle",
|
||||||
|
# Minecraft 指令类
|
||||||
|
"MineCommand",
|
||||||
|
# 默认进度条样式
|
||||||
|
"DEFAULT_PROGRESSBAR_STYLE",
|
||||||
|
]
|
||||||
646
Musicreater/builtin_plugins/to_commands/main.py
Normal file
646
Musicreater/builtin_plugins/to_commands/main.py
Normal file
@@ -0,0 +1,646 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 指令生成插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos, MineNote
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
library_plugin,
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
LibraryPluginBase,
|
||||||
|
)
|
||||||
|
from Musicreater.exceptions import ZeroSpeedError, IllegalMinimumVolumeError
|
||||||
|
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||||
|
|
||||||
|
from .progressbar import ProgressBarStyle, DEFAULT_PROGRESSBAR_STYLE, mctick2timestr
|
||||||
|
from .utils import minenote_to_command_parameters
|
||||||
|
|
||||||
|
|
||||||
|
# @dataclass
|
||||||
|
# class CommandConvertionConfig(PluginConfig):
|
||||||
|
# execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run "
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MineCommand:
|
||||||
|
"""存储单个指令的类"""
|
||||||
|
|
||||||
|
command: str
|
||||||
|
"""指令文本"""
|
||||||
|
conditional: bool = False
|
||||||
|
"""执行是否有条件"""
|
||||||
|
delay: int = 0
|
||||||
|
"""执行的延迟"""
|
||||||
|
annotation: str = ""
|
||||||
|
"""指令注释"""
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return MineCommand(
|
||||||
|
command=self.command,
|
||||||
|
conditional=self.conditional,
|
||||||
|
delay=self.delay,
|
||||||
|
annotation=self.annotation,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mcfunction_command_string(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,
|
||||||
|
cmd=self.command,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other) -> bool:
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
# 不比较注释内容
|
||||||
|
return (
|
||||||
|
(self.command == other.command)
|
||||||
|
and (self.conditional == other.conditional)
|
||||||
|
and (self.delay == other.delay)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@library_plugin("notedata_to_command_plugin")
|
||||||
|
class NoteDataConvert2CommandPlugin(LibraryPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="音符数据指令支持插件",
|
||||||
|
author="金羿、玉衡Alioth",
|
||||||
|
description="从音符数据转换为我的世界指令相关格式",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.LIBRARY,
|
||||||
|
license="Same as Musicreater",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 暂时没有适配动画内容和替换顺序
|
||||||
|
# 金羿正在处理这个,不需要改
|
||||||
|
# 但是返回值和接口内容不会变,直接用即可
|
||||||
|
#
|
||||||
|
@staticmethod
|
||||||
|
def generate_progressbar(
|
||||||
|
max_score: int,
|
||||||
|
scoreboard_name: str,
|
||||||
|
music_name: str = "",
|
||||||
|
progressbar_style: ProgressBarStyle = DEFAULT_PROGRESSBAR_STYLE,
|
||||||
|
execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ",
|
||||||
|
) -> List[MineCommand]:
|
||||||
|
"""
|
||||||
|
生成进度条
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
max_score: int
|
||||||
|
最大的积分值
|
||||||
|
scoreboard_name: str
|
||||||
|
所使用的计分板名称
|
||||||
|
progressbar_style: ProgressBarStyle
|
||||||
|
此参数详见 ../docs/库的生成与功能文档.md#进度条自定义
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[MineCommand,]
|
||||||
|
"""
|
||||||
|
orignal_style_string = progressbar_style.style_base_string
|
||||||
|
"""用于被替换的进度条原始样式"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
| 标识符 | 指定的可变量 |
|
||||||
|
|---------|----------------|
|
||||||
|
| `%%N` | 乐曲名 |
|
||||||
|
| `%^s` | 计分板最大值 |
|
||||||
|
| `%^t` | 曲目总时长 |
|
||||||
|
| `%%s` | 当前计分板值 |
|
||||||
|
| `%%t` | 当前播放时间 |
|
||||||
|
| `%%%` | 当前进度比率 |
|
||||||
|
| `_` | 用以表示进度条占位|
|
||||||
|
| `%*%` | 指定*的动画内容 |
|
||||||
|
"""
|
||||||
|
per_value_in_each = max_score / orignal_style_string.count("_")
|
||||||
|
"""每个进度条代表的分值"""
|
||||||
|
|
||||||
|
result: List[MineCommand] = []
|
||||||
|
|
||||||
|
orignal_style_string = (
|
||||||
|
orignal_style_string.replace("%%N", music_name)
|
||||||
|
.replace("%^s", str(max_score))
|
||||||
|
.replace("%^t", mctick2timestr(max_score))
|
||||||
|
)
|
||||||
|
|
||||||
|
scoreboard_name_part = scoreboard_name[:2]
|
||||||
|
if "%%%" in orignal_style_string:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
'scoreboard objectives add {}PercT dummy "百分比计算"'.format(
|
||||||
|
scoreboard_name_part
|
||||||
|
),
|
||||||
|
annotation="新增临时百分比变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set MaxScore {} {}".format(
|
||||||
|
scoreboard_name, max_score
|
||||||
|
),
|
||||||
|
annotation="设定音乐最大延迟分数",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set n100 {} 100".format(scoreboard_name),
|
||||||
|
annotation="设置常量100",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} = @s {}".format(
|
||||||
|
scoreboard_name_part + "PercT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="赋值当前进度",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} *= n100 {}".format(
|
||||||
|
scoreboard_name_part + "PercT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="转换当前进度之单位至百分比(扩大精度)",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} /= MaxScore {}".format(
|
||||||
|
scoreboard_name_part + "PercT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="计算进度百分比",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%%t" in orignal_style_string:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format(
|
||||||
|
scoreboard_name_part
|
||||||
|
),
|
||||||
|
annotation="新增临时分变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(
|
||||||
|
scoreboard_name_part
|
||||||
|
),
|
||||||
|
annotation="新增临时秒变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set n20 {} 20".format(scoreboard_name),
|
||||||
|
annotation="设置常量 20",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set n60 {} 60".format(scoreboard_name),
|
||||||
|
annotation="设置常量 60",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} = @s {}".format(
|
||||||
|
scoreboard_name_part + "TMinT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="赋值临时分变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} /= n20 {}".format(
|
||||||
|
scoreboard_name_part + "TMinT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="转换临时分变量之单位为秒(缩减精度)",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} = @s {}".format(
|
||||||
|
scoreboard_name_part + "TSecT", scoreboard_name_part + "TMinT"
|
||||||
|
),
|
||||||
|
annotation="赋值临时秒",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} /= n60 {}".format(
|
||||||
|
scoreboard_name_part + "TMinT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="转换临时分变量之单位为分(缩减精度)",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} %= n60 {}".format(
|
||||||
|
scoreboard_name_part + "TSecT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="确定临时秒(框定精度区间)",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if progressbar_style.is_animate_autoloop and progressbar_style.animate_circle:
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
'scoreboard objectives add {}AniC dummy "动画循环控制"'.format(
|
||||||
|
scoreboard_name_part
|
||||||
|
),
|
||||||
|
annotation="新增动画循环控制变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for animate_placeholder in progressbar_style.animate_circle:
|
||||||
|
max_loop_score = max(
|
||||||
|
progressbar_style.animate_circle[animate_placeholder].keys()
|
||||||
|
)
|
||||||
|
if ("%%%" not in orignal_style_string or max_loop_score != 100) and (
|
||||||
|
"%%t" not in orignal_style_string or max_loop_score not in (60, 20)
|
||||||
|
):
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set n{num} {sbn} {num}".format(
|
||||||
|
sbn=scoreboard_name,
|
||||||
|
num=max_loop_score,
|
||||||
|
),
|
||||||
|
annotation="设置常量 {num}".format(num=max_loop_score),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {sbnp}AniC = @s {sbn}".format(
|
||||||
|
sbnp=scoreboard_name_part, sbn=scoreboard_name
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {sbnp}AniC %= n{num} {sbn}".format(
|
||||||
|
sbnp=scoreboard_name_part,
|
||||||
|
num=max_loop_score,
|
||||||
|
sbn=scoreboard_name,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in range(orignal_style_string.count("_")):
|
||||||
|
npg_stl = (
|
||||||
|
orignal_style_string.replace(
|
||||||
|
"%%s",
|
||||||
|
'"},{"score":{"name":"*","objective":"'
|
||||||
|
+ scoreboard_name
|
||||||
|
+ '"}},{"text":"',
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"%%t",
|
||||||
|
'"},{"score":{"name":"*","objective":"{-}TMinT"}},{"text":":"},'
|
||||||
|
'{"score":{"name":"*","objective":"{-}TSecT"}},{"text":"'.replace(
|
||||||
|
"{-}", scoreboard_name_part
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"%%%",
|
||||||
|
'"},{"score":{"name":"*","objective":"'
|
||||||
|
+ scoreboard_name_part
|
||||||
|
+ 'PercT"}},{"text":"%',
|
||||||
|
)
|
||||||
|
.replace("_", progressbar_style.progress_played, i + 1)
|
||||||
|
.replace("_", progressbar_style.progress_toplay)
|
||||||
|
)
|
||||||
|
for animate_placeholder in progressbar_style.animate_circle:
|
||||||
|
animation_start_tick = 0
|
||||||
|
npg_stl = npg_stl.replace(
|
||||||
|
animate_placeholder,
|
||||||
|
'"},{"translate": "%%'
|
||||||
|
+ str(
|
||||||
|
len(progressbar_style.animate_circle[animate_placeholder]) + 1
|
||||||
|
)
|
||||||
|
+ '","with":{"rawtext":['
|
||||||
|
+ (
|
||||||
|
",".join(
|
||||||
|
(
|
||||||
|
'{"selector":"@s[scores={{-}={*}..{&}}]"}'.replace(
|
||||||
|
"{*}", str(animation_start_tick)
|
||||||
|
).replace("{&}", str(animation_start_tick := end_tick))
|
||||||
|
for end_tick in progressbar_style.animate_circle[
|
||||||
|
animate_placeholder
|
||||||
|
].keys()
|
||||||
|
)
|
||||||
|
).replace(
|
||||||
|
"{-}",
|
||||||
|
(
|
||||||
|
(scoreboard_name_part + "AniC")
|
||||||
|
if progressbar_style.is_animate_autoloop
|
||||||
|
else scoreboard_name
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
+ ","
|
||||||
|
+ ",".join(
|
||||||
|
(
|
||||||
|
'{"text":"' + animation_text + '"}'
|
||||||
|
for animation_text in progressbar_style.animate_circle[
|
||||||
|
animate_placeholder
|
||||||
|
].values()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
+ ',{"text":"NaN"}]}},{"text":"',
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={"
|
||||||
|
+ scoreboard_name
|
||||||
|
+ f"={int(i * per_value_in_each)}..{ceil((i + 1) * per_value_in_each)}"
|
||||||
|
+ "}]"
|
||||||
|
)
|
||||||
|
+ 'titleraw @s actionbar {"rawtext":[{"text":"'
|
||||||
|
+ npg_stl
|
||||||
|
+ '"}]}',
|
||||||
|
annotation="进度条显示",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%%%" in orignal_style_string:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
"scoreboard objectives remove {}PercT".format(scoreboard_name_part),
|
||||||
|
annotation="移除临时百分比变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if "%%t" in orignal_style_string:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
"scoreboard objectives remove {}TMinT".format(scoreboard_name_part),
|
||||||
|
annotation="移除临时分变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
"scoreboard objectives remove {}TSecT".format(scoreboard_name_part),
|
||||||
|
annotation="移除临时秒变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if progressbar_style.is_animate_autoloop and progressbar_style.animate_circle:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
"scoreboard objectives remove {}AniC".format(scoreboard_name_part),
|
||||||
|
annotation="移除临时动画循环控制变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_command_list_in_score(
|
||||||
|
music: SingleMusic,
|
||||||
|
music_deviation: float = 0,
|
||||||
|
minimum_volume: float = 0.01,
|
||||||
|
scoreboard_name: str = "mscplay",
|
||||||
|
execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ",
|
||||||
|
) -> Tuple[List[List[MineCommand]], int, int]:
|
||||||
|
"""
|
||||||
|
将midi转换为我的世界命令列表
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
scoreboard_name: str
|
||||||
|
我的世界的计分板名称
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple( list[list[MineCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
|
||||||
|
"""
|
||||||
|
|
||||||
|
command_channels: List[List[MineCommand]] = []
|
||||||
|
command_amount = 0
|
||||||
|
max_score = 0
|
||||||
|
|
||||||
|
for track in music.music_tracks:
|
||||||
|
# 如果当前轨道为空 则跳过
|
||||||
|
if not track:
|
||||||
|
continue
|
||||||
|
|
||||||
|
this_channel = []
|
||||||
|
|
||||||
|
for note in track.minenotes:
|
||||||
|
max_score = max(max_score, note.start_tick)
|
||||||
|
|
||||||
|
(
|
||||||
|
relative_coordinates,
|
||||||
|
volume_percentage,
|
||||||
|
mc_pitch,
|
||||||
|
) = minenote_to_command_parameters(
|
||||||
|
note,
|
||||||
|
pitch_deviation=music_deviation,
|
||||||
|
)
|
||||||
|
|
||||||
|
this_channel.append(
|
||||||
|
MineCommand(
|
||||||
|
(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores=({}={})]".format(
|
||||||
|
scoreboard_name, note.start_tick
|
||||||
|
)
|
||||||
|
.replace("(", r"{")
|
||||||
|
.replace(")", r"}")
|
||||||
|
)
|
||||||
|
+ r"playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
||||||
|
track.instrument,
|
||||||
|
*relative_coordinates,
|
||||||
|
volume_percentage,
|
||||||
|
1.0 if note.percussive else mc_pitch,
|
||||||
|
minimum_volume,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
annotation=(
|
||||||
|
"[{}] 打击乐音符{}".format(
|
||||||
|
mctick2timestr(note.start_tick),
|
||||||
|
note.instrument,
|
||||||
|
)
|
||||||
|
if note.percussive
|
||||||
|
else "[{}] 音符{}:{:.2f}".format(
|
||||||
|
mctick2timestr(note.start_tick),
|
||||||
|
note.instrument,
|
||||||
|
mc_pitch,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
command_amount += 1
|
||||||
|
|
||||||
|
if this_channel:
|
||||||
|
command_channels.append(this_channel)
|
||||||
|
|
||||||
|
return command_channels, command_amount, max_score
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_command_list_in_delay(
|
||||||
|
music: SingleMusic,
|
||||||
|
music_deviation: float = 0,
|
||||||
|
minimum_volume: float = 0.01,
|
||||||
|
player_selector: str = "@a",
|
||||||
|
execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ",
|
||||||
|
) -> Tuple[List[MineCommand], int, int]:
|
||||||
|
"""
|
||||||
|
将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
player_selector: str
|
||||||
|
玩家选择器,默认为`@a`
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple( list[MineCommand指令,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 音轨判断
|
||||||
|
music_command_list = []
|
||||||
|
multi = max_multi = 0
|
||||||
|
delaytime_previous = 0
|
||||||
|
last_note: MineNote
|
||||||
|
|
||||||
|
for note in music.get_minenotes(
|
||||||
|
start_time=0,
|
||||||
|
):
|
||||||
|
if (tickdelay := (note.start_tick - delaytime_previous)) == 0:
|
||||||
|
multi += 1
|
||||||
|
else:
|
||||||
|
max_multi = max(max_multi, multi)
|
||||||
|
multi = 0
|
||||||
|
|
||||||
|
(
|
||||||
|
relative_coordinates,
|
||||||
|
volume_percentage,
|
||||||
|
mc_pitch,
|
||||||
|
) = minenote_to_command_parameters(
|
||||||
|
note,
|
||||||
|
pitch_deviation=music_deviation,
|
||||||
|
)
|
||||||
|
|
||||||
|
music_command_list.append(
|
||||||
|
MineCommand(
|
||||||
|
command=(
|
||||||
|
execute_command_head.format(player_selector)
|
||||||
|
+ "playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
||||||
|
note.instrument,
|
||||||
|
*relative_coordinates,
|
||||||
|
volume_percentage,
|
||||||
|
1.0 if note.percussive else mc_pitch,
|
||||||
|
minimum_volume,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
annotation=(
|
||||||
|
"[{}] 打击乐音符{}".format(
|
||||||
|
mctick2timestr(note.start_tick),
|
||||||
|
note.instrument,
|
||||||
|
)
|
||||||
|
if note.percussive
|
||||||
|
else "[{}] 音符{}:{:.2f}".format(
|
||||||
|
mctick2timestr(note.start_tick),
|
||||||
|
note.instrument,
|
||||||
|
mc_pitch,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
delay=tickdelay,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
delaytime_previous = note.start_tick
|
||||||
|
last_note = note
|
||||||
|
if music_command_list:
|
||||||
|
return (
|
||||||
|
music_command_list,
|
||||||
|
last_note.start_tick + last_note.duration_tick,
|
||||||
|
max_multi + 1,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return [], 0, 0
|
||||||
179
Musicreater/builtin_plugins/to_commands/progressbar.py
Normal file
179
Musicreater/builtin_plugins/to_commands/progressbar.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 指令生成插件的进度条相关内容
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 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 BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping
|
||||||
|
|
||||||
|
|
||||||
|
# 这个类也有很大的优化空间a
|
||||||
|
@dataclass(init=False)
|
||||||
|
class ProgressBarStyle:
|
||||||
|
"""进度条样式类"""
|
||||||
|
|
||||||
|
style_base_string: str
|
||||||
|
"""基础样式"""
|
||||||
|
|
||||||
|
progress_toplay: str
|
||||||
|
"""未播放之样式"""
|
||||||
|
|
||||||
|
progress_played: str
|
||||||
|
"""已播放之样式"""
|
||||||
|
|
||||||
|
is_animate_autoloop: bool
|
||||||
|
"""所示动画是否循环"""
|
||||||
|
|
||||||
|
animate_circle: Dict[str, Dict[int, str]]
|
||||||
|
"""
|
||||||
|
定义动画样式
|
||||||
|
Dict[占位符, Dict[截止时间刻, 样式字符串]]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
base_string: str = "【%%N】%A%▶ %%s/%^s (%%t|%^t) \n"
|
||||||
|
"[§e_________________________§r] %%%",
|
||||||
|
to_play_style: str = "§7=",
|
||||||
|
played_style: str = "=",
|
||||||
|
animate_loop: bool = True,
|
||||||
|
animate_circle: Dict[str, Dict[int, str]] = {
|
||||||
|
"%A%": {5: "-", 10: "\\\\", 15: "|", 20: "/"}
|
||||||
|
},
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
用于存储进度条样式的类,标识符替换顺序如下表
|
||||||
|
|
||||||
|
| 标识符 | 指定的可变量 |
|
||||||
|
|---------|----------------|
|
||||||
|
| `%%N` | 乐曲名 |
|
||||||
|
| `%^s` | 计分板最大值 |
|
||||||
|
| `%^t` | 曲目总时长 |
|
||||||
|
| `%%s` | 当前计分板值 |
|
||||||
|
| `%%t` | 当前播放时间 |
|
||||||
|
| `%%%` | 当前进度比率 |
|
||||||
|
| `_` | 用以表示进度条占位|
|
||||||
|
| `%*%` | 指定*的动画内容 |
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
base_string: str
|
||||||
|
基础样式,用以定义进度条整体
|
||||||
|
to_play_style: str
|
||||||
|
进度条样式:尚未播放的样子
|
||||||
|
played_style: str
|
||||||
|
已经播放的样子
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
ProgressBarStyle 类
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.style_base_string = base_string
|
||||||
|
self.progress_toplay = to_play_style
|
||||||
|
self.progress_played = played_style
|
||||||
|
self.is_animate_autoloop = animate_loop
|
||||||
|
self.animate_circle = animate_circle
|
||||||
|
|
||||||
|
def set_base_style(self, value: str):
|
||||||
|
"""设置基础样式"""
|
||||||
|
self.style_base_string = value
|
||||||
|
|
||||||
|
def set_to_play_style(self, value: str):
|
||||||
|
"""设置未播放之样式"""
|
||||||
|
self.progress_toplay = value
|
||||||
|
|
||||||
|
def set_played_style(self, value: str):
|
||||||
|
"""设置已播放之样式"""
|
||||||
|
self.progress_played = value
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return ProgressBarStyle(
|
||||||
|
self.style_base_string,
|
||||||
|
self.progress_toplay,
|
||||||
|
self.progress_played,
|
||||||
|
self.is_animate_autoloop,
|
||||||
|
self.animate_circle,
|
||||||
|
)
|
||||||
|
|
||||||
|
def play_output(
|
||||||
|
self,
|
||||||
|
played_ticks: int,
|
||||||
|
total_ticks: int,
|
||||||
|
music_name: str = "无题",
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
直接依照此格式输出一个进度条
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
played_delays: int
|
||||||
|
当前播放进度积分值
|
||||||
|
total_delays: int
|
||||||
|
乐器总延迟数(计分板值)
|
||||||
|
music_name: str
|
||||||
|
曲名
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
str
|
||||||
|
进度条字符串
|
||||||
|
"""
|
||||||
|
|
||||||
|
alpha_string = (
|
||||||
|
self.style_base_string.replace("%%N", music_name)
|
||||||
|
.replace("%%s", str(played_ticks))
|
||||||
|
.replace("%^s", str(total_ticks))
|
||||||
|
.replace("%%t", mctick2timestr(played_ticks))
|
||||||
|
.replace("%^t", mctick2timestr(total_ticks))
|
||||||
|
.replace(
|
||||||
|
"%%%",
|
||||||
|
"{:0>5.2f}%".format(int(10000 * played_ticks / total_ticks) / 100),
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"_",
|
||||||
|
self.progress_played,
|
||||||
|
(played_ticks * self.style_base_string.count("_") // total_ticks) + 1,
|
||||||
|
)
|
||||||
|
.replace("_", self.progress_toplay)
|
||||||
|
)
|
||||||
|
|
||||||
|
for key, animate_dict in self.animate_circle.items():
|
||||||
|
max_animate_tick = max(animate_dict.keys())
|
||||||
|
if self.is_animate_autoloop:
|
||||||
|
animate_time_key = 0
|
||||||
|
for time_key in animate_dict.keys():
|
||||||
|
animate_time_key = time_key
|
||||||
|
if time_key > played_ticks % max_animate_tick:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
animate_time_key = max_animate_tick
|
||||||
|
alpha_string = alpha_string.replace(key, animate_dict[animate_time_key])
|
||||||
|
return alpha_string
|
||||||
|
|
||||||
|
|
||||||
|
def mctick2timestr(mc_tick: int) -> str:
|
||||||
|
"""
|
||||||
|
将《我的世界》的游戏刻计转为表示时间的字符串
|
||||||
|
"""
|
||||||
|
return "{:0>2d}:{:0>2d}".format(mc_tick // 1200, (mc_tick // 20) % 60)
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle()
|
||||||
|
"""
|
||||||
|
默认的进度条样式
|
||||||
|
"""
|
||||||
115
Musicreater/builtin_plugins/to_commands/utils.py
Normal file
115
Musicreater/builtin_plugins/to_commands/utils.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的指令生成插件的功能方法
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
BinaryIO,
|
||||||
|
Optional,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Callable,
|
||||||
|
Tuple,
|
||||||
|
Mapping,
|
||||||
|
Union,
|
||||||
|
Literal,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater import MineNote, SingleNote
|
||||||
|
from Musicreater.constants import MM_INSTRUMENT_DEVIATION_TABLE
|
||||||
|
|
||||||
|
|
||||||
|
# 这个函数可以直接被优化成一个只处理音调参数的,没必要完整留着
|
||||||
|
def minenote_to_command_parameters(
|
||||||
|
mine_note: MineNote,
|
||||||
|
pitch_deviation: float = 0,
|
||||||
|
) -> Tuple[
|
||||||
|
Tuple[float, float, float],
|
||||||
|
float,
|
||||||
|
Union[float, Literal[None]],
|
||||||
|
]:
|
||||||
|
"""
|
||||||
|
将 MineNote 对象转为《我的世界》音符播放所需之参数
|
||||||
|
|
||||||
|
参数
|
||||||
|
----
|
||||||
|
mine_note: MineNote
|
||||||
|
我的世界音符对象
|
||||||
|
pitch_deviation: float
|
||||||
|
音调偏移量
|
||||||
|
|
||||||
|
返回
|
||||||
|
----
|
||||||
|
tuple[float, float, float], float, float
|
||||||
|
播放视角坐标, 指令音量参数, 指令音调参数
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
mine_note.position.position_displacement,
|
||||||
|
mine_note.volume / 127,
|
||||||
|
(
|
||||||
|
None
|
||||||
|
if mine_note.percussive
|
||||||
|
else (
|
||||||
|
2
|
||||||
|
** (
|
||||||
|
(
|
||||||
|
mine_note.pitch
|
||||||
|
- 60
|
||||||
|
- MM_INSTRUMENT_DEVIATION_TABLE.get(mine_note.instrument, 6)
|
||||||
|
+ pitch_deviation
|
||||||
|
)
|
||||||
|
/ 12
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_minecraft_pitch(
|
||||||
|
note: MineNote, pitch_deviation: float = 0
|
||||||
|
) -> Optional[float]:
|
||||||
|
"""
|
||||||
|
计算音符的音调参数
|
||||||
|
|
||||||
|
参数
|
||||||
|
----
|
||||||
|
note: MineNote
|
||||||
|
我的世界音符对象
|
||||||
|
deviation: float
|
||||||
|
音调偏移量
|
||||||
|
|
||||||
|
返回
|
||||||
|
----
|
||||||
|
Optional[float]
|
||||||
|
音调参数, 当为打击乐器时为 None
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
None
|
||||||
|
if note.percussive
|
||||||
|
else (
|
||||||
|
2
|
||||||
|
** (
|
||||||
|
(
|
||||||
|
note.pitch
|
||||||
|
- 60
|
||||||
|
- MM_INSTRUMENT_DEVIATION_TABLE.get(note.instrument, 6)
|
||||||
|
+ pitch_deviation
|
||||||
|
)
|
||||||
|
/ 12
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -34,25 +34,9 @@ z = "z"
|
|||||||
z
|
z
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MIDI_PROGRAM = "program"
|
|
||||||
"""Midi乐器编号"""
|
|
||||||
|
|
||||||
MIDI_VOLUME = "volume"
|
|
||||||
"""Midi通道音量"""
|
|
||||||
|
|
||||||
MIDI_PAN = "pan"
|
|
||||||
"""Midi通道立体声场偏移"""
|
|
||||||
|
|
||||||
|
|
||||||
# Midi用对照表
|
# Midi用对照表
|
||||||
|
|
||||||
MIDI_DEFAULT_VOLUME_VALUE: int = (
|
|
||||||
64 # Midi默认音量,当用户未指定时,默认使用折中默认音量
|
|
||||||
)
|
|
||||||
|
|
||||||
MIDI_DEFAULT_PROGRAM_VALUE: int = (
|
|
||||||
74 # 当 Midi 本身与用户皆未指定音色时,默认 Flute 长笛
|
|
||||||
)
|
|
||||||
|
|
||||||
MIDI_PITCH_NAME_TABLE: Dict[int, str] = {
|
MIDI_PITCH_NAME_TABLE: Dict[int, str] = {
|
||||||
0: "C",
|
0: "C",
|
||||||
@@ -528,770 +512,10 @@ MM_INSTRUMENT_DEVIATION_TABLE: Dict[str, int] = {
|
|||||||
*注意* 该表中的单位是对于 Midi Pitch 音调(整数)的低音偏移。
|
*注意* 该表中的单位是对于 Midi Pitch 音调(整数)的低音偏移。
|
||||||
也就是说,该数值越高,则在 Midi Pitch 中的值域越低
|
也就是说,该数值越高,则在 Midi Pitch 中的值域越低
|
||||||
默认的偏移量为 6 ,因为在计算音高时候少减去了 6 个 Pitch 单位
|
默认的偏移量为 6 ,因为在计算音高时候少减去了 6 个 Pitch 单位
|
||||||
|
(在表里的数据是用作被减数的,实际计算时默认有 +6,所以在表中默认的 6 最后就会被抵消)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Midi乐器对MC乐器对照表
|
|
||||||
|
|
||||||
# “经典”对照表,由 Chalie Ping “查理平” 和 金羿ELS 提供
|
|
||||||
|
|
||||||
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
|
||||||
0: "note.harp",
|
|
||||||
1: "note.harp",
|
|
||||||
2: "note.pling",
|
|
||||||
3: "note.harp",
|
|
||||||
4: "note.pling",
|
|
||||||
5: "note.pling",
|
|
||||||
6: "note.harp",
|
|
||||||
7: "note.harp",
|
|
||||||
8: "note.snare",
|
|
||||||
9: "note.harp",
|
|
||||||
10: "note.didgeridoo",
|
|
||||||
11: "note.harp",
|
|
||||||
12: "note.xylophone",
|
|
||||||
13: "note.chime",
|
|
||||||
14: "note.harp",
|
|
||||||
15: "note.harp",
|
|
||||||
16: "note.bass",
|
|
||||||
17: "note.harp",
|
|
||||||
18: "note.harp",
|
|
||||||
19: "note.harp",
|
|
||||||
20: "note.harp",
|
|
||||||
21: "note.harp",
|
|
||||||
22: "note.harp",
|
|
||||||
23: "note.guitar",
|
|
||||||
24: "note.guitar",
|
|
||||||
25: "note.guitar",
|
|
||||||
26: "note.guitar",
|
|
||||||
27: "note.guitar",
|
|
||||||
28: "note.guitar",
|
|
||||||
29: "note.guitar",
|
|
||||||
30: "note.guitar",
|
|
||||||
31: "note.bass",
|
|
||||||
32: "note.bass",
|
|
||||||
33: "note.bass",
|
|
||||||
34: "note.bass",
|
|
||||||
35: "note.bass",
|
|
||||||
36: "note.bass",
|
|
||||||
37: "note.bass",
|
|
||||||
38: "note.bass",
|
|
||||||
39: "note.bass",
|
|
||||||
40: "note.harp",
|
|
||||||
41: "note.harp",
|
|
||||||
42: "note.harp",
|
|
||||||
43: "note.harp",
|
|
||||||
44: "note.iron_xylophone",
|
|
||||||
45: "note.guitar",
|
|
||||||
46: "note.harp",
|
|
||||||
47: "note.harp",
|
|
||||||
48: "note.guitar",
|
|
||||||
49: "note.guitar",
|
|
||||||
50: "note.bit",
|
|
||||||
51: "note.bit",
|
|
||||||
52: "note.harp",
|
|
||||||
53: "note.harp",
|
|
||||||
54: "note.bit",
|
|
||||||
55: "note.flute",
|
|
||||||
56: "note.flute",
|
|
||||||
57: "note.flute",
|
|
||||||
58: "note.flute",
|
|
||||||
59: "note.flute",
|
|
||||||
60: "note.flute",
|
|
||||||
61: "note.flute",
|
|
||||||
62: "note.flute",
|
|
||||||
63: "note.flute",
|
|
||||||
64: "note.bit",
|
|
||||||
65: "note.bit",
|
|
||||||
66: "note.bit",
|
|
||||||
67: "note.bit",
|
|
||||||
68: "note.flute",
|
|
||||||
69: "note.harp",
|
|
||||||
70: "note.harp",
|
|
||||||
71: "note.flute",
|
|
||||||
72: "note.flute",
|
|
||||||
73: "note.flute",
|
|
||||||
74: "note.harp",
|
|
||||||
75: "note.flute",
|
|
||||||
76: "note.harp",
|
|
||||||
77: "note.harp",
|
|
||||||
78: "note.harp",
|
|
||||||
79: "note.harp",
|
|
||||||
80: "note.bit",
|
|
||||||
81: "note.bit",
|
|
||||||
82: "note.bit",
|
|
||||||
83: "note.bit",
|
|
||||||
84: "note.bit",
|
|
||||||
85: "note.bit",
|
|
||||||
86: "note.bit",
|
|
||||||
87: "note.bit",
|
|
||||||
88: "note.bit",
|
|
||||||
89: "note.bit",
|
|
||||||
90: "note.bit",
|
|
||||||
91: "note.bit",
|
|
||||||
92: "note.bit",
|
|
||||||
93: "note.bit",
|
|
||||||
94: "note.bit",
|
|
||||||
95: "note.bit",
|
|
||||||
96: "note.bit",
|
|
||||||
97: "note.bit",
|
|
||||||
98: "note.bit",
|
|
||||||
99: "note.bit",
|
|
||||||
100: "note.bit",
|
|
||||||
101: "note.bit",
|
|
||||||
102: "note.bit",
|
|
||||||
103: "note.bit",
|
|
||||||
104: "note.harp",
|
|
||||||
105: "note.banjo",
|
|
||||||
106: "note.harp",
|
|
||||||
107: "note.harp",
|
|
||||||
108: "note.harp",
|
|
||||||
109: "note.harp",
|
|
||||||
110: "note.harp",
|
|
||||||
111: "note.guitar",
|
|
||||||
112: "note.harp",
|
|
||||||
113: "note.bell",
|
|
||||||
114: "note.harp",
|
|
||||||
115: "note.cow_bell",
|
|
||||||
116: "note.bd",
|
|
||||||
117: "note.bass",
|
|
||||||
118: "note.bit",
|
|
||||||
119: "note.bd",
|
|
||||||
120: "note.guitar",
|
|
||||||
121: "note.harp",
|
|
||||||
122: "note.harp",
|
|
||||||
123: "note.harp",
|
|
||||||
124: "note.harp",
|
|
||||||
125: "note.hat",
|
|
||||||
126: "note.bd",
|
|
||||||
127: "note.snare",
|
|
||||||
}
|
|
||||||
"""“经典”乐音乐器对照表"""
|
|
||||||
|
|
||||||
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
|
||||||
34: "note.bd",
|
|
||||||
35: "note.bd",
|
|
||||||
36: "note.hat",
|
|
||||||
37: "note.snare",
|
|
||||||
38: "note.snare",
|
|
||||||
39: "note.snare",
|
|
||||||
40: "note.hat",
|
|
||||||
41: "note.snare",
|
|
||||||
42: "note.hat",
|
|
||||||
43: "note.snare",
|
|
||||||
44: "note.snare",
|
|
||||||
45: "note.bell",
|
|
||||||
46: "note.snare",
|
|
||||||
47: "note.snare",
|
|
||||||
48: "note.bell",
|
|
||||||
49: "note.hat",
|
|
||||||
50: "note.bell",
|
|
||||||
51: "note.bell",
|
|
||||||
52: "note.bell",
|
|
||||||
53: "note.bell",
|
|
||||||
54: "note.bell",
|
|
||||||
55: "note.bell",
|
|
||||||
56: "note.snare",
|
|
||||||
57: "note.hat",
|
|
||||||
58: "note.chime",
|
|
||||||
59: "note.iron_xylophone",
|
|
||||||
60: "note.bd",
|
|
||||||
61: "note.bd",
|
|
||||||
62: "note.xylophone",
|
|
||||||
63: "note.xylophone",
|
|
||||||
64: "note.xylophone",
|
|
||||||
65: "note.hat",
|
|
||||||
66: "note.bell",
|
|
||||||
67: "note.bell",
|
|
||||||
68: "note.hat",
|
|
||||||
69: "note.hat",
|
|
||||||
70: "note.snare",
|
|
||||||
71: "note.flute",
|
|
||||||
72: "note.hat",
|
|
||||||
73: "note.hat",
|
|
||||||
74: "note.xylophone",
|
|
||||||
75: "note.hat",
|
|
||||||
76: "note.hat",
|
|
||||||
77: "note.xylophone",
|
|
||||||
78: "note.xylophone",
|
|
||||||
79: "note.bell",
|
|
||||||
80: "note.bell",
|
|
||||||
}
|
|
||||||
"""“经典”打击乐器对照表"""
|
|
||||||
|
|
||||||
# Touch “偷吃” 高准确率音色对照表
|
|
||||||
|
|
||||||
MM_TOUCH_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
|
||||||
0: "note.harp",
|
|
||||||
1: "note.harp",
|
|
||||||
2: "note.pling",
|
|
||||||
3: "note.harp",
|
|
||||||
4: "note.pling",
|
|
||||||
5: "note.pling",
|
|
||||||
6: "note.guitar",
|
|
||||||
7: "note.guitar",
|
|
||||||
8: "note.iron_xylophone",
|
|
||||||
9: "note.bell",
|
|
||||||
10: "note.iron_xylophone",
|
|
||||||
11: "note.iron_xylophone",
|
|
||||||
12: "note.iron_xylophone",
|
|
||||||
13: "note.xylophone",
|
|
||||||
14: "note.chime",
|
|
||||||
15: "note.banjo",
|
|
||||||
16: "note.xylophone",
|
|
||||||
17: "note.iron_xylophone",
|
|
||||||
18: "note.flute",
|
|
||||||
19: "note.flute",
|
|
||||||
20: "note.flute",
|
|
||||||
21: "note.flute",
|
|
||||||
22: "note.flute",
|
|
||||||
23: "note.flute",
|
|
||||||
24: "note.guitar",
|
|
||||||
25: "note.guitar",
|
|
||||||
26: "note.guitar",
|
|
||||||
27: "note.guitar",
|
|
||||||
28: "note.guitar",
|
|
||||||
29: "note.guitar",
|
|
||||||
30: "note.guitar",
|
|
||||||
31: "note.bass",
|
|
||||||
32: "note.bass",
|
|
||||||
33: "note.bass",
|
|
||||||
34: "note.bass",
|
|
||||||
35: "note.bass",
|
|
||||||
36: "note.bass",
|
|
||||||
37: "note.bass",
|
|
||||||
38: "note.bass",
|
|
||||||
39: "note.bass",
|
|
||||||
40: "note.flute",
|
|
||||||
41: "note.flute",
|
|
||||||
42: "note.flute",
|
|
||||||
43: "note.bass",
|
|
||||||
44: "note.flute",
|
|
||||||
45: "note.iron_xylophone",
|
|
||||||
46: "note.harp",
|
|
||||||
47: "note.snare",
|
|
||||||
48: "note.flute",
|
|
||||||
49: "note.flute",
|
|
||||||
50: "note.flute",
|
|
||||||
51: "note.flute",
|
|
||||||
52: "note.didgeridoo",
|
|
||||||
53: "note.flute",
|
|
||||||
54: "note.flute",
|
|
||||||
55: "mob.zombie.wood",
|
|
||||||
56: "note.flute",
|
|
||||||
57: "note.flute",
|
|
||||||
58: "note.flute",
|
|
||||||
59: "note.flute",
|
|
||||||
60: "note.flute",
|
|
||||||
61: "note.flute",
|
|
||||||
62: "note.flute",
|
|
||||||
63: "note.flute",
|
|
||||||
64: "note.bit",
|
|
||||||
65: "note.bit",
|
|
||||||
66: "note.bit",
|
|
||||||
67: "note.bit",
|
|
||||||
68: "note.flute",
|
|
||||||
69: "note.bit",
|
|
||||||
70: "note.banjo",
|
|
||||||
71: "note.flute",
|
|
||||||
72: "note.flute",
|
|
||||||
73: "note.flute",
|
|
||||||
74: "note.flute",
|
|
||||||
75: "note.flute",
|
|
||||||
76: "note.iron_xylophone",
|
|
||||||
77: "note.iron_xylophone",
|
|
||||||
78: "note.flute",
|
|
||||||
79: "note.flute",
|
|
||||||
80: "note.bit",
|
|
||||||
81: "note.bit",
|
|
||||||
82: "note.flute",
|
|
||||||
83: "note.flute",
|
|
||||||
84: "note.guitar",
|
|
||||||
85: "note.flute",
|
|
||||||
86: "note.bass",
|
|
||||||
87: "note.bass",
|
|
||||||
88: "note.bit",
|
|
||||||
89: "note.flute",
|
|
||||||
90: "note.bit",
|
|
||||||
91: "note.flute",
|
|
||||||
92: "note.bell",
|
|
||||||
93: "note.guitar",
|
|
||||||
94: "note.flute",
|
|
||||||
95: "note.bit",
|
|
||||||
96: "note.bit",
|
|
||||||
97: "note.flute",
|
|
||||||
98: "note.bell",
|
|
||||||
99: "note.bit",
|
|
||||||
100: "note.bit",
|
|
||||||
101: "note.bit",
|
|
||||||
102: "note.bit",
|
|
||||||
103: "note.bit",
|
|
||||||
104: "note.iron_xylophone",
|
|
||||||
105: "note.banjo",
|
|
||||||
106: "note.harp",
|
|
||||||
107: "note.harp",
|
|
||||||
108: "note.bell",
|
|
||||||
109: "note.flute",
|
|
||||||
110: "note.flute",
|
|
||||||
111: "note.flute",
|
|
||||||
112: "note.bell",
|
|
||||||
113: "note.xylophone",
|
|
||||||
114: "note.flute",
|
|
||||||
115: "note.hat",
|
|
||||||
116: "note.snare",
|
|
||||||
117: "note.snare",
|
|
||||||
118: "note.bd",
|
|
||||||
119: "firework.blast",
|
|
||||||
120: "note.guitar",
|
|
||||||
121: "note.harp",
|
|
||||||
122: "note.harp",
|
|
||||||
123: "note.harp",
|
|
||||||
124: "note.bit",
|
|
||||||
125: "note.hat",
|
|
||||||
126: "firework.twinkle",
|
|
||||||
127: "mob.zombie.wood",
|
|
||||||
}
|
|
||||||
"""“偷吃”乐音乐器对照表"""
|
|
||||||
|
|
||||||
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
|
||||||
34: "note.hat",
|
|
||||||
35: "note.bd",
|
|
||||||
36: "note.bd",
|
|
||||||
37: "note.snare",
|
|
||||||
38: "note.snare",
|
|
||||||
39: "fire.ignite",
|
|
||||||
40: "note.snare",
|
|
||||||
41: "note.hat",
|
|
||||||
42: "note.hat",
|
|
||||||
43: "firework.blast",
|
|
||||||
44: "note.hat",
|
|
||||||
45: "note.snare",
|
|
||||||
46: "note.snare",
|
|
||||||
47: "note.snare",
|
|
||||||
48: "note.bell",
|
|
||||||
49: "note.hat",
|
|
||||||
50: "note.bell",
|
|
||||||
51: "note.bell",
|
|
||||||
52: "note.bell",
|
|
||||||
53: "note.bell",
|
|
||||||
54: "note.bell",
|
|
||||||
55: "note.bell",
|
|
||||||
56: "note.snare",
|
|
||||||
57: "note.hat",
|
|
||||||
58: "note.chime",
|
|
||||||
59: "note.iron_xylophone",
|
|
||||||
60: "note.bd",
|
|
||||||
61: "note.bd",
|
|
||||||
62: "note.xylophone",
|
|
||||||
63: "note.xylophone",
|
|
||||||
64: "note.xylophone",
|
|
||||||
65: "note.hat",
|
|
||||||
66: "note.bell",
|
|
||||||
67: "note.bell",
|
|
||||||
68: "note.hat",
|
|
||||||
69: "note.hat",
|
|
||||||
70: "note.snare",
|
|
||||||
71: "note.flute",
|
|
||||||
72: "note.hat",
|
|
||||||
73: "note.hat",
|
|
||||||
74: "note.xylophone",
|
|
||||||
75: "note.hat",
|
|
||||||
76: "note.hat",
|
|
||||||
77: "note.xylophone",
|
|
||||||
78: "note.xylophone",
|
|
||||||
79: "note.bell",
|
|
||||||
80: "note.bell",
|
|
||||||
}
|
|
||||||
"""“偷吃”打击乐器对照表"""
|
|
||||||
|
|
||||||
# Dislink “断联” 音色对照表
|
|
||||||
# https://github.com/Dislink/midi2bdx/blob/main/index.html
|
|
||||||
|
|
||||||
|
|
||||||
MM_DISLINK_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
|
||||||
0: "note.harp",
|
|
||||||
1: "note.harp",
|
|
||||||
2: "note.pling",
|
|
||||||
3: "note.harp",
|
|
||||||
4: "note.harp",
|
|
||||||
5: "note.harp",
|
|
||||||
6: "note.harp",
|
|
||||||
7: "note.harp",
|
|
||||||
8: "note.iron_xylophone",
|
|
||||||
9: "note.bell",
|
|
||||||
10: "note.iron_xylophone",
|
|
||||||
11: "note.iron_xylophone",
|
|
||||||
12: "note.iron_xylophone",
|
|
||||||
13: "note.iron_xylophone",
|
|
||||||
14: "note.chime",
|
|
||||||
15: "note.iron_xylophone",
|
|
||||||
16: "note.harp",
|
|
||||||
17: "note.harp",
|
|
||||||
18: "note.harp",
|
|
||||||
19: "note.harp",
|
|
||||||
20: "note.harp",
|
|
||||||
21: "note.harp",
|
|
||||||
22: "note.harp",
|
|
||||||
23: "note.harp",
|
|
||||||
24: "note.guitar",
|
|
||||||
25: "note.guitar",
|
|
||||||
26: "note.guitar",
|
|
||||||
27: "note.guitar",
|
|
||||||
28: "note.guitar",
|
|
||||||
29: "note.guitar",
|
|
||||||
30: "note.guitar",
|
|
||||||
31: "note.guitar",
|
|
||||||
32: "note.bass",
|
|
||||||
33: "note.bass",
|
|
||||||
34: "note.bass",
|
|
||||||
35: "note.bass",
|
|
||||||
36: "note.bass",
|
|
||||||
37: "note.bass",
|
|
||||||
38: "note.bass",
|
|
||||||
39: "note.bass",
|
|
||||||
40: "note.harp",
|
|
||||||
41: "note.flute",
|
|
||||||
42: "note.flute",
|
|
||||||
43: "note.flute",
|
|
||||||
44: "note.flute",
|
|
||||||
45: "note.harp",
|
|
||||||
46: "note.harp",
|
|
||||||
47: "note.harp",
|
|
||||||
48: "note.harp",
|
|
||||||
49: "note.harp",
|
|
||||||
50: "note.harp",
|
|
||||||
51: "note.harp",
|
|
||||||
52: "note.harp",
|
|
||||||
53: "note.harp",
|
|
||||||
54: "note.harp",
|
|
||||||
55: "note.harp",
|
|
||||||
56: "note.harp",
|
|
||||||
57: "note.harp",
|
|
||||||
58: "note.harp",
|
|
||||||
59: "note.harp",
|
|
||||||
60: "note.harp",
|
|
||||||
61: "note.harp",
|
|
||||||
62: "note.harp",
|
|
||||||
63: "note.harp",
|
|
||||||
64: "note.harp",
|
|
||||||
65: "note.harp",
|
|
||||||
66: "note.harp",
|
|
||||||
67: "note.harp",
|
|
||||||
68: "note.harp",
|
|
||||||
69: "note.harp",
|
|
||||||
70: "note.harp",
|
|
||||||
71: "note.harp",
|
|
||||||
72: "note.flute",
|
|
||||||
73: "note.flute",
|
|
||||||
74: "note.flute",
|
|
||||||
75: "note.flute",
|
|
||||||
76: "note.flute",
|
|
||||||
77: "note.flute",
|
|
||||||
78: "note.flute",
|
|
||||||
79: "note.flute",
|
|
||||||
80: "note.bit",
|
|
||||||
81: "note.bit",
|
|
||||||
82: "note.harp",
|
|
||||||
83: "note.harp",
|
|
||||||
84: "note.harp",
|
|
||||||
85: "note.harp",
|
|
||||||
86: "note.harp",
|
|
||||||
87: "note.harp",
|
|
||||||
88: "note.harp",
|
|
||||||
89: "note.harp",
|
|
||||||
90: "note.harp",
|
|
||||||
91: "note.harp",
|
|
||||||
92: "note.harp",
|
|
||||||
93: "note.harp",
|
|
||||||
94: "note.harp",
|
|
||||||
95: "note.harp",
|
|
||||||
96: "note.harp",
|
|
||||||
97: "note.harp",
|
|
||||||
98: "note.harp",
|
|
||||||
99: "note.harp",
|
|
||||||
100: "note.harp",
|
|
||||||
101: "note.harp",
|
|
||||||
102: "note.harp",
|
|
||||||
103: "note.harp",
|
|
||||||
104: "note.harp",
|
|
||||||
105: "note.banjo",
|
|
||||||
106: "note.harp",
|
|
||||||
107: "note.harp",
|
|
||||||
108: "note.harp",
|
|
||||||
109: "note.harp",
|
|
||||||
110: "note.harp",
|
|
||||||
111: "note.harp",
|
|
||||||
112: "note.cow_bell",
|
|
||||||
113: "note.harp",
|
|
||||||
114: "note.harp",
|
|
||||||
115: "note.bd",
|
|
||||||
116: "note.bd",
|
|
||||||
117: "note.bd",
|
|
||||||
118: "note.bd",
|
|
||||||
119: "note.harp",
|
|
||||||
120: "note.harp",
|
|
||||||
121: "note.harp",
|
|
||||||
122: "note.harp",
|
|
||||||
123: "note.harp",
|
|
||||||
124: "note.harp",
|
|
||||||
125: "note.harp",
|
|
||||||
126: "note.harp",
|
|
||||||
127: "note.harp",
|
|
||||||
}
|
|
||||||
"""“断联”乐音乐器对照表"""
|
|
||||||
|
|
||||||
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
|
||||||
34: "note.bd",
|
|
||||||
35: "note.bd",
|
|
||||||
36: "note.snare",
|
|
||||||
37: "note.snare",
|
|
||||||
38: "note.bd",
|
|
||||||
39: "note.snare",
|
|
||||||
40: "note.bd",
|
|
||||||
41: "note.hat",
|
|
||||||
42: "note.bd",
|
|
||||||
43: "note.hat",
|
|
||||||
44: "note.bd",
|
|
||||||
45: "note.hat",
|
|
||||||
46: "note.bd",
|
|
||||||
47: "note.bd",
|
|
||||||
48: "note.bd",
|
|
||||||
49: "note.bd",
|
|
||||||
50: "note.bd",
|
|
||||||
51: "note.bd",
|
|
||||||
52: "note.bd",
|
|
||||||
53: "note.bd",
|
|
||||||
54: "note.bd",
|
|
||||||
55: "note.cow_bell",
|
|
||||||
56: "note.bd",
|
|
||||||
57: "note.bd",
|
|
||||||
58: "note.bd",
|
|
||||||
59: "note.bd",
|
|
||||||
60: "note.bd",
|
|
||||||
61: "note.bd",
|
|
||||||
62: "note.bd",
|
|
||||||
63: "note.bd",
|
|
||||||
64: "note.bd",
|
|
||||||
65: "note.bd",
|
|
||||||
66: "note.bd",
|
|
||||||
67: "note.bd",
|
|
||||||
68: "note.bd",
|
|
||||||
69: "note.bd",
|
|
||||||
70: "note.bd",
|
|
||||||
71: "note.bd",
|
|
||||||
72: "note.bd",
|
|
||||||
73: "note.bd",
|
|
||||||
74: "note.bd",
|
|
||||||
75: "note.bd",
|
|
||||||
76: "note.bd",
|
|
||||||
77: "note.bd",
|
|
||||||
78: "note.bd",
|
|
||||||
79: "note.bd",
|
|
||||||
80: "note.bd",
|
|
||||||
}
|
|
||||||
"""“断联”打击乐器对照表"""
|
|
||||||
|
|
||||||
# NoteBlockStudio “NBS”音色对照表
|
|
||||||
# https://github.com/OpenNBS/NoteBlockStudio/blob/main/scripts/midi_instruments/midi_instruments.gml
|
|
||||||
|
|
||||||
MM_NBS_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
|
||||||
0: "note.harp",
|
|
||||||
1: "note.pling",
|
|
||||||
2: "note.harp",
|
|
||||||
3: "note.pling",
|
|
||||||
4: "note.harp",
|
|
||||||
5: "note.harp",
|
|
||||||
6: "note.guitar",
|
|
||||||
7: "note.banjo",
|
|
||||||
8: "note.bell",
|
|
||||||
9: "note.bell",
|
|
||||||
10: "note.bell",
|
|
||||||
11: "note.iron_xylophone",
|
|
||||||
12: "note.iron_xylophone",
|
|
||||||
13: "note.xylophone",
|
|
||||||
14: "note.bell",
|
|
||||||
15: "note.iron_xylophone",
|
|
||||||
16: "note.flute",
|
|
||||||
17: "note.flute",
|
|
||||||
18: "note.flute",
|
|
||||||
19: "note.flute",
|
|
||||||
20: "note.flute",
|
|
||||||
21: "note.flute",
|
|
||||||
22: "note.flute",
|
|
||||||
23: "note.flute",
|
|
||||||
24: "note.guitar",
|
|
||||||
25: "note.guitar",
|
|
||||||
26: "note.guitar",
|
|
||||||
27: "note.bass",
|
|
||||||
28: "note.guitar",
|
|
||||||
29: "note.guitar",
|
|
||||||
30: "note.bass",
|
|
||||||
31: "note.bass",
|
|
||||||
32: "note.bass",
|
|
||||||
33: "note.guitar",
|
|
||||||
34: "note.guitar",
|
|
||||||
35: "note.bass",
|
|
||||||
36: "note.pling",
|
|
||||||
37: "note.flute",
|
|
||||||
38: "note.flute",
|
|
||||||
39: "note.flute",
|
|
||||||
40: "note.flute",
|
|
||||||
41: "note.flute",
|
|
||||||
42: "note.didgeridoo",
|
|
||||||
43: "note.flute",
|
|
||||||
44: "note.didgeridoo",
|
|
||||||
45: "note.flute",
|
|
||||||
46: "note.flute",
|
|
||||||
47: "note.flute",
|
|
||||||
48: "note.flute",
|
|
||||||
49: "note.flute",
|
|
||||||
50: "note.flute",
|
|
||||||
51: "note.flute",
|
|
||||||
52: "note.flute",
|
|
||||||
53: "note.flute",
|
|
||||||
54: "note.flute",
|
|
||||||
55: "note.flute",
|
|
||||||
56: "note.flute",
|
|
||||||
57: "note.flute",
|
|
||||||
58: "note.flute",
|
|
||||||
59: "note.flute",
|
|
||||||
60: "note.bit",
|
|
||||||
61: "note.flute",
|
|
||||||
62: "note.flute",
|
|
||||||
63: "note.flute",
|
|
||||||
64: "note.flute",
|
|
||||||
65: "note.guitar",
|
|
||||||
66: "note.flute",
|
|
||||||
67: "note.flute",
|
|
||||||
68: "note.flute",
|
|
||||||
69: "note.bell",
|
|
||||||
70: "note.flute",
|
|
||||||
71: "note.flute",
|
|
||||||
72: "note.flute",
|
|
||||||
73: "note.flute",
|
|
||||||
74: "note.chime",
|
|
||||||
75: "note.flute",
|
|
||||||
76: "note.flute",
|
|
||||||
77: "note.guitar",
|
|
||||||
78: "note.pling",
|
|
||||||
79: "note.flute",
|
|
||||||
80: "note.guitar",
|
|
||||||
81: "note.banjo",
|
|
||||||
82: "note.banjo",
|
|
||||||
83: "note.banjo",
|
|
||||||
84: "note.guitar",
|
|
||||||
85: "note.iron_xylophone",
|
|
||||||
86: "note.flute",
|
|
||||||
87: "note.flute",
|
|
||||||
88: "note.chime",
|
|
||||||
89: "note.cow_bell",
|
|
||||||
90: "note.iron_xylophone",
|
|
||||||
91: "note.xylophone",
|
|
||||||
92: "note.basedrum",
|
|
||||||
93: "note.snare",
|
|
||||||
94: "note.snare",
|
|
||||||
95: "note.basedrum",
|
|
||||||
96: "note.snare",
|
|
||||||
97: "note.hat",
|
|
||||||
98: "note.snare",
|
|
||||||
99: "note.hat",
|
|
||||||
100: "note.basedrum",
|
|
||||||
101: "note.hat",
|
|
||||||
102: "note.basedrum",
|
|
||||||
103: "note.hat",
|
|
||||||
104: "note.basedrum",
|
|
||||||
105: "note.snare",
|
|
||||||
106: "note.snare",
|
|
||||||
107: "note.snare",
|
|
||||||
108: "note.cow_bell",
|
|
||||||
109: "note.snare",
|
|
||||||
110: "note.hat",
|
|
||||||
111: "note.snare",
|
|
||||||
112: "note.hat",
|
|
||||||
113: "note.hat",
|
|
||||||
114: "note.hat",
|
|
||||||
115: "note.hat",
|
|
||||||
116: "note.hat",
|
|
||||||
117: "note.chime",
|
|
||||||
118: "note.hat",
|
|
||||||
119: "note.snare",
|
|
||||||
120: "note.hat",
|
|
||||||
121: "note.hat",
|
|
||||||
122: "note.hat",
|
|
||||||
123: "note.hat",
|
|
||||||
124: "note.hat",
|
|
||||||
125: "note.snare",
|
|
||||||
126: "note.basedrum",
|
|
||||||
127: "note.basedrum",
|
|
||||||
}
|
|
||||||
"""“NBS”乐音乐器对照表"""
|
|
||||||
|
|
||||||
|
|
||||||
MM_NBS_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
|
||||||
24: "note.bit",
|
|
||||||
25: "note.snare",
|
|
||||||
26: "note.hat",
|
|
||||||
27: "note.snare",
|
|
||||||
28: "note.snare",
|
|
||||||
29: "note.hat",
|
|
||||||
30: "note.hat",
|
|
||||||
31: "note.hat",
|
|
||||||
32: "note.hat",
|
|
||||||
33: "note.hat",
|
|
||||||
34: "note.chime",
|
|
||||||
35: "note.basedrum",
|
|
||||||
36: "note.basedrum",
|
|
||||||
37: "note.hat",
|
|
||||||
38: "note.snare",
|
|
||||||
39: "note.hat",
|
|
||||||
40: "note.snare",
|
|
||||||
41: "note.basedrum",
|
|
||||||
42: "note.snare",
|
|
||||||
43: "note.basedrum",
|
|
||||||
44: "note.snare",
|
|
||||||
45: "note.basedrum",
|
|
||||||
46: "note.basedrum",
|
|
||||||
47: "note.snare",
|
|
||||||
48: "note.snare",
|
|
||||||
49: "note.snare",
|
|
||||||
50: "note.snare",
|
|
||||||
51: "note.snare",
|
|
||||||
52: "note.snare",
|
|
||||||
53: "note.hat",
|
|
||||||
54: "note.snare",
|
|
||||||
55: "note.snare",
|
|
||||||
56: "note.cow_bell",
|
|
||||||
57: "note.snare",
|
|
||||||
58: "note.hat",
|
|
||||||
59: "note.snare",
|
|
||||||
60: "note.hat",
|
|
||||||
61: "note.hat",
|
|
||||||
62: "note.hat",
|
|
||||||
63: "note.basedrum",
|
|
||||||
64: "note.basedrum",
|
|
||||||
65: "note.snare",
|
|
||||||
66: "note.snare",
|
|
||||||
67: "note.xylophone",
|
|
||||||
68: "note.xylophone",
|
|
||||||
69: "note.hat",
|
|
||||||
70: "note.hat",
|
|
||||||
71: "note.flute",
|
|
||||||
72: "note.flute",
|
|
||||||
73: "note.hat",
|
|
||||||
74: "note.hat",
|
|
||||||
75: "note.hat",
|
|
||||||
76: "note.hat",
|
|
||||||
77: "note.hat",
|
|
||||||
78: "note.didgeridoo",
|
|
||||||
79: "note.didgeridoo",
|
|
||||||
80: "note.hat",
|
|
||||||
81: "note.chime",
|
|
||||||
82: "note.hat",
|
|
||||||
83: "note.chime",
|
|
||||||
84: "note.chime",
|
|
||||||
85: "note.hat",
|
|
||||||
86: "note.basedrum",
|
|
||||||
87: "note.basedrum",
|
|
||||||
}
|
|
||||||
"""“NBS”打击乐器对照表"""
|
|
||||||
|
|
||||||
# Midi音高对MC方块对照表
|
# Midi音高对MC方块对照表
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 的内部数据类
|
音·创 v3 的内部数据类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -41,6 +41,7 @@ from typing import (
|
|||||||
Literal,
|
Literal,
|
||||||
Hashable,
|
Hashable,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
|
Mapping,
|
||||||
)
|
)
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
@@ -71,12 +72,14 @@ class SoundAtmos:
|
|||||||
------------
|
------------
|
||||||
distance: float
|
distance: float
|
||||||
发声源距离玩家的距离(半径 `r`)
|
发声源距离玩家的距离(半径 `r`)
|
||||||
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
|
注:距离越近,音量越高,默认为 0。此参数可以作为音轨的音量使用。
|
||||||
|
音量若默认为 +0,则此值当为 8;此值最小为 0.01,最大为 16。
|
||||||
azimuth: tuple[float, float]
|
azimuth: tuple[float, float]
|
||||||
声源方位
|
声源方位
|
||||||
注:此参数为tuple,包含两个元素,分别表示:
|
注:此参数为tuple,包含两个元素,分别表示:
|
||||||
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
||||||
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上
|
||||||
|
(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
|
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
|
||||||
@@ -123,16 +126,21 @@ class SoundAtmos:
|
|||||||
dk1 * round(cos(radians(self.sound_azimuth[0])), 8),
|
dk1 * round(cos(radians(self.sound_azimuth[0])), 8),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "SoundAtmos(d={}, rV={}, rH={})".format(
|
||||||
|
self.sound_distance, *self.sound_azimuth
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class SingleNote:
|
class SingleNote:
|
||||||
"""存储单个音符的类"""
|
"""存储单个音符的类"""
|
||||||
|
|
||||||
note_pitch: int
|
midi_pitch: int
|
||||||
"""midi音高"""
|
"""Midi 音高"""
|
||||||
|
|
||||||
velocity: int
|
volume: int
|
||||||
"""力度"""
|
"""力度/播放响度 0~127 百廿七分比"""
|
||||||
|
|
||||||
start_time: int
|
start_time: int
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
@@ -148,10 +156,10 @@ class SingleNote:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
midi_pitch: Optional[int],
|
note_pitch: Optional[int],
|
||||||
midi_velocity: int,
|
note_volume: int,
|
||||||
start_time: int,
|
start_tick: int,
|
||||||
last_time: int,
|
keep_tick: int,
|
||||||
mass_precision_time: int = 0,
|
mass_precision_time: int = 0,
|
||||||
extra_information: Dict[str, Any] = {},
|
extra_information: Dict[str, Any] = {},
|
||||||
):
|
):
|
||||||
@@ -161,9 +169,9 @@ class SingleNote:
|
|||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
midi_pitch: int
|
midi_pitch: int
|
||||||
midi音高
|
Midi 音高
|
||||||
midi_velocity: int
|
note_volume: int
|
||||||
midi响度(力度)
|
响度/力度(百廿七分比, 0~127)
|
||||||
start_time: int
|
start_time: int
|
||||||
开始之时(命令刻)
|
开始之时(命令刻)
|
||||||
注:此处的时间是用从乐曲开始到当前的刻数
|
注:此处的时间是用从乐曲开始到当前的刻数
|
||||||
@@ -181,13 +189,13 @@ class SingleNote:
|
|||||||
MineNote 类
|
MineNote 类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.note_pitch: int = 66 if midi_pitch is None else midi_pitch
|
self.midi_pitch: int = 66 if note_pitch is None else note_pitch
|
||||||
"""midi音高"""
|
"""Midi 音高"""
|
||||||
self.velocity: int = midi_velocity
|
self.volume: int = note_volume
|
||||||
"""响度(力度)"""
|
"""响度(力度)"""
|
||||||
self.start_time: int = start_time
|
self.start_time: int = start_tick
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
self.duration: int = last_time
|
self.duration: int = keep_tick
|
||||||
"""音符持续时间 命令刻"""
|
"""音符持续时间 命令刻"""
|
||||||
self.high_precision_start_time: int = mass_precision_time
|
self.high_precision_start_time: int = mass_precision_time
|
||||||
"""高精度开始时间偏量 0.4 毫秒"""
|
"""高精度开始时间偏量 0.4 毫秒"""
|
||||||
@@ -201,15 +209,15 @@ class SingleNote:
|
|||||||
group_1 := int.from_bytes(code_buffer[:6], "big")
|
group_1 := int.from_bytes(code_buffer[:6], "big")
|
||||||
) & 0b11111111111111111
|
) & 0b11111111111111111
|
||||||
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
|
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
|
||||||
note_velocity_ = (group_1 := group_1 >> 17) & 0b1111111
|
note_volume_ = (group_1 := group_1 >> 17) & 0b1111111
|
||||||
note_pitch_ = (group_1 := group_1 >> 7) & 0b1111111
|
note_pitch_ = (group_1 := group_1 >> 7) & 0b1111111
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return cls(
|
return cls(
|
||||||
midi_pitch=note_pitch_,
|
note_pitch=note_pitch_,
|
||||||
midi_velocity=note_velocity_,
|
note_volume=note_volume_,
|
||||||
start_time=start_tick_,
|
start_tick=start_tick_,
|
||||||
last_time=duration_,
|
keep_tick=duration_,
|
||||||
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
|
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -222,11 +230,10 @@ class SingleNote:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
raise SingleNoteDecodeError(
|
raise SingleNoteDecodeError(
|
||||||
e,
|
|
||||||
"技术信息:\nGROUP1\t`{}`\nCODE_BUFFER\t`{}`".format(
|
"技术信息:\nGROUP1\t`{}`\nCODE_BUFFER\t`{}`".format(
|
||||||
group_1, code_buffer
|
group_1, code_buffer
|
||||||
),
|
),
|
||||||
)
|
) from e
|
||||||
|
|
||||||
def encode(self, is_high_time_precision: bool = True) -> bytes:
|
def encode(self, is_high_time_precision: bool = True) -> bytes:
|
||||||
"""
|
"""
|
||||||
@@ -246,7 +253,7 @@ class SingleNote:
|
|||||||
# SingleNote 的字节码
|
# SingleNote 的字节码
|
||||||
|
|
||||||
# note_pitch 7 位 支持到 127
|
# note_pitch 7 位 支持到 127
|
||||||
# velocity 长度 7 位 支持到 127
|
# volume 长度 7 位 支持到 127
|
||||||
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
||||||
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
||||||
# 共 48 位 合 6 字节
|
# 共 48 位 合 6 字节
|
||||||
@@ -256,7 +263,7 @@ class SingleNote:
|
|||||||
return (
|
return (
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
((((self.note_pitch << 7) + self.velocity) << 17) + self.start_time)
|
((((self.midi_pitch << 7) + self.volume) << 17) + self.start_time)
|
||||||
<< 17
|
<< 17
|
||||||
)
|
)
|
||||||
+ self.duration
|
+ self.duration
|
||||||
@@ -291,9 +298,9 @@ class SingleNote:
|
|||||||
return self.extra_info.get(key, default)
|
return self.extra_info.get(key, default)
|
||||||
|
|
||||||
def stringize(self, include_extra_data: bool = False) -> str:
|
def stringize(self, include_extra_data: bool = False) -> str:
|
||||||
return "TrackedNote(Pitch = {}, Velocity = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
|
return "TrackedNote(Pitch = {}, Volume = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
|
||||||
self.note_pitch,
|
self.midi_pitch,
|
||||||
self.velocity,
|
self.volume,
|
||||||
self.start_time,
|
self.start_time,
|
||||||
self.duration,
|
self.duration,
|
||||||
self.high_precision_start_time,
|
self.high_precision_start_time,
|
||||||
@@ -308,8 +315,8 @@ class SingleNote:
|
|||||||
self,
|
self,
|
||||||
) -> Tuple[int, int, int, int, int]:
|
) -> Tuple[int, int, int, int, int]:
|
||||||
return (
|
return (
|
||||||
self.note_pitch,
|
self.midi_pitch,
|
||||||
self.velocity,
|
self.volume,
|
||||||
self.start_time,
|
self.start_time,
|
||||||
self.duration,
|
self.duration,
|
||||||
self.high_precision_start_time,
|
self.high_precision_start_time,
|
||||||
@@ -317,40 +324,39 @@ class SingleNote:
|
|||||||
|
|
||||||
def __dict__(self):
|
def __dict__(self):
|
||||||
return {
|
return {
|
||||||
"Pitch": self.note_pitch,
|
"Pitch": self.midi_pitch,
|
||||||
"Velocity": self.velocity,
|
"Volume": self.volume,
|
||||||
"StartTick": self.start_time,
|
"StartTick": self.start_time,
|
||||||
"Duration": self.duration,
|
"Duration": self.duration,
|
||||||
"TimeOffset": self.high_precision_start_time,
|
"TimeOffset": self.high_precision_start_time,
|
||||||
"ExtraData": self.extra_info,
|
"ExtraData": self.extra_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other: "SingleNote") -> bool:
|
||||||
"""比较两个音符是否具有相同的属性,不计附加信息"""
|
"""比较两个音符是否具有相同的属性,不计附加信息"""
|
||||||
if not isinstance(other, self.__class__):
|
if not isinstance(other, self.__class__):
|
||||||
return False
|
return False
|
||||||
return self.__tuple__() == other.__tuple__()
|
return self.__tuple__() == other.__tuple__()
|
||||||
|
|
||||||
def __lt__(self, other) -> bool:
|
def __lt__(self, other: "SingleNote") -> bool:
|
||||||
"""比较自己是否在开始时间上早于另一个音符"""
|
"""比较自己是否在开始时间上早于另一个音符"""
|
||||||
if self.start_time == other.start_tick:
|
if self.start_time == other.start_time:
|
||||||
return self.high_precision_start_time < other.high_precision_time
|
return self.high_precision_start_time < other.high_precision_start_time
|
||||||
else:
|
else:
|
||||||
return self.start_time < other.start_tick
|
return self.start_time < other.start_time
|
||||||
|
|
||||||
def __gt__(self, other) -> bool:
|
def __gt__(self, other: "SingleNote") -> bool:
|
||||||
"""比较自己是否在开始时间上晚于另一个音符"""
|
"""比较自己是否在开始时间上晚于另一个音符"""
|
||||||
if self.start_time == other.start_tick:
|
if self.start_time == other.start_time:
|
||||||
return self.high_precision_start_time > other.high_precision_time
|
return self.high_precision_start_time > other.high_precision_start_time
|
||||||
else:
|
else:
|
||||||
return self.start_time > other.start_tick
|
return self.start_time > other.start_time
|
||||||
|
|
||||||
|
|
||||||
class CurvableParam(str, Enum):
|
class CurvableParam(str, Enum):
|
||||||
"""可曲线化的参数枚举类"""
|
"""可曲线化的参数 枚举类"""
|
||||||
|
|
||||||
PITCH = "adjust_note_pitch"
|
PITCH = "adjust_note_pitch"
|
||||||
VELOCITY = "adjust_note_velocity"
|
|
||||||
VOLUME = "adjust_note_volume"
|
VOLUME = "adjust_note_volume"
|
||||||
DISTANCE = "adjust_note_sound_distance"
|
DISTANCE = "adjust_note_sound_distance"
|
||||||
LR_PANNING = "adjust_note_leftright_panning_degree"
|
LR_PANNING = "adjust_note_leftright_panning_degree"
|
||||||
@@ -362,13 +368,11 @@ class MineNote:
|
|||||||
"""我的世界音符对象(仅提供我的世界相关接口)"""
|
"""我的世界音符对象(仅提供我的世界相关接口)"""
|
||||||
|
|
||||||
pitch: float
|
pitch: float
|
||||||
"""midi音高"""
|
"""Midi 音高"""
|
||||||
instrument: str
|
instrument: str
|
||||||
"""乐器ID"""
|
"""乐器 ID"""
|
||||||
velocity: float
|
|
||||||
"""力度"""
|
|
||||||
volume: float
|
volume: float
|
||||||
"""音量"""
|
"""力度/播放音量 0~127 百廿七分比"""
|
||||||
start_tick: int
|
start_tick: int
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
duration_tick: int
|
duration_tick: int
|
||||||
@@ -385,12 +389,10 @@ class MineNote:
|
|||||||
cls,
|
cls,
|
||||||
note: SingleNote,
|
note: SingleNote,
|
||||||
note_instrument: str,
|
note_instrument: str,
|
||||||
sound_volume: float,
|
|
||||||
is_persiced_time: bool,
|
is_persiced_time: bool,
|
||||||
is_percussive_note: bool,
|
is_percussive_note: bool,
|
||||||
sound_position: SoundAtmos,
|
sound_position: SoundAtmos,
|
||||||
adjust_note_pitch: float = 0.0,
|
adjust_note_pitch: float = 0.0,
|
||||||
adjust_note_velocity: float = 0.0,
|
|
||||||
adjust_note_volume: float = 0.0,
|
adjust_note_volume: float = 0.0,
|
||||||
adjust_note_sound_distance: float = 0.0,
|
adjust_note_sound_distance: float = 0.0,
|
||||||
adjust_note_leftright_panning_degree: float = 0.0,
|
adjust_note_leftright_panning_degree: float = 0.0,
|
||||||
@@ -403,10 +405,9 @@ class MineNote:
|
|||||||
sound_position.sound_azimuth[1] + adjust_note_updown_panning_degree,
|
sound_position.sound_azimuth[1] + adjust_note_updown_panning_degree,
|
||||||
)
|
)
|
||||||
return cls(
|
return cls(
|
||||||
pitch=note.note_pitch + adjust_note_pitch,
|
pitch=note.midi_pitch + adjust_note_pitch,
|
||||||
instrument=note_instrument,
|
instrument=note_instrument,
|
||||||
velocity=note.velocity + adjust_note_velocity,
|
volume=note.volume + adjust_note_volume,
|
||||||
volume=sound_volume + adjust_note_volume,
|
|
||||||
start_tick=note.start_time,
|
start_tick=note.start_time,
|
||||||
duration_tick=note.duration,
|
duration_tick=note.duration,
|
||||||
persiced_time=note.high_precision_start_time if is_persiced_time else 0,
|
persiced_time=note.high_precision_start_time if is_persiced_time else 0,
|
||||||
@@ -418,18 +419,15 @@ class MineNote:
|
|||||||
class SingleTrack(List[SingleNote]):
|
class SingleTrack(List[SingleNote]):
|
||||||
"""存储单个轨道的类"""
|
"""存储单个轨道的类"""
|
||||||
|
|
||||||
track_name: str
|
name: str
|
||||||
"""轨道之名称"""
|
"""轨道之名称"""
|
||||||
|
|
||||||
is_enabled: bool = True
|
is_enabled: bool = True
|
||||||
"""该音轨是否启用"""
|
"""该音轨是否启用"""
|
||||||
|
|
||||||
track_instrument: str
|
instrument: str
|
||||||
"""乐器ID"""
|
"""乐器ID"""
|
||||||
|
|
||||||
track_volume: float
|
|
||||||
"""该音轨的音量"""
|
|
||||||
|
|
||||||
is_high_time_precision: bool
|
is_high_time_precision: bool
|
||||||
"""该音轨是否使用高精度时间"""
|
"""该音轨是否使用高精度时间"""
|
||||||
|
|
||||||
@@ -447,31 +445,28 @@ class SingleTrack(List[SingleNote]):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str = "未命名音轨",
|
*args: SingleNote,
|
||||||
instrument: str = "",
|
track_name: str = "未命名音轨",
|
||||||
volume: float = 0,
|
track_instrument: str = "",
|
||||||
precise_time: bool = True,
|
precise_time: bool = True,
|
||||||
percussion: bool = False,
|
percussion: bool = False,
|
||||||
sound_direction: SoundAtmos = SoundAtmos(),
|
sound_direction: Optional[SoundAtmos] = None,
|
||||||
extra_information: Dict[str, Any] = {},
|
extra_information: Dict[str, Any] = {},
|
||||||
*args: SingleNote,
|
|
||||||
):
|
):
|
||||||
self.track_name = name
|
self.name = track_name
|
||||||
"""音轨名称"""
|
"""音轨名称"""
|
||||||
|
|
||||||
self.track_instrument = instrument
|
self.instrument = track_instrument
|
||||||
"""乐器ID"""
|
"""乐器ID"""
|
||||||
|
|
||||||
self.track_volume = volume
|
|
||||||
"""音量"""
|
|
||||||
|
|
||||||
self.is_high_time_precision = precise_time
|
self.is_high_time_precision = precise_time
|
||||||
"""是否使用高精度时间"""
|
"""是否使用高精度时间"""
|
||||||
|
|
||||||
self.is_percussive = percussion
|
self.is_percussive = percussion
|
||||||
"""是否为打击乐器"""
|
"""是否为打击乐器"""
|
||||||
|
|
||||||
self.sound_position = sound_direction
|
# 如果不这样的话,所有的新的 SingleTrack 类都会有一个共同的声像方位
|
||||||
|
self.sound_position = sound_direction if sound_direction else SoundAtmos()
|
||||||
"""声像方位"""
|
"""声像方位"""
|
||||||
|
|
||||||
self.extra_info = extra_information if extra_information else {}
|
self.extra_info = extra_information if extra_information else {}
|
||||||
@@ -531,7 +526,7 @@ class SingleTrack(List[SingleNote]):
|
|||||||
|
|
||||||
def get_notes(
|
def get_notes(
|
||||||
self, start_time: float, end_time: float = inf
|
self, start_time: float, end_time: float = inf
|
||||||
) -> Generator[SingleNote, None, None]:
|
) -> Iterator[SingleNote]:
|
||||||
"""通过开始时间和结束时间来获取音符"""
|
"""通过开始时间和结束时间来获取音符"""
|
||||||
if end_time < start_time:
|
if end_time < start_time:
|
||||||
raise ParameterValueError(
|
raise ParameterValueError(
|
||||||
@@ -539,12 +534,13 @@ class SingleTrack(List[SingleNote]):
|
|||||||
end_time, start_time
|
end_time, start_time
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif start_time < 0 or end_time < 0:
|
elif end_time < 0:
|
||||||
raise ParameterValueError(
|
raise ParameterValueError(
|
||||||
"获取音符的时间范围有误,终止时间`{}`和起始时间`{}`皆不可为负数".format(
|
"获取音符的时间范围有误,终止时间`{}`不可为负数".format(end_time)
|
||||||
end_time, start_time
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
elif start_time <= 0 and end_time >= self[-1].start_time:
|
||||||
|
return iter(self)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
x
|
x
|
||||||
for x in self
|
for x in self
|
||||||
@@ -559,8 +555,7 @@ class SingleTrack(List[SingleNote]):
|
|||||||
for _note in self.get_notes(range_start_time, range_end_time):
|
for _note in self.get_notes(range_start_time, range_end_time):
|
||||||
yield MineNote.from_single_note(
|
yield MineNote.from_single_note(
|
||||||
note=_note,
|
note=_note,
|
||||||
note_instrument=self.track_instrument,
|
note_instrument=self.instrument,
|
||||||
sound_volume=self.track_volume,
|
|
||||||
is_persiced_time=self.is_high_time_precision,
|
is_persiced_time=self.is_high_time_precision,
|
||||||
is_percussive_note=self.is_percussive,
|
is_percussive_note=self.is_percussive,
|
||||||
sound_position=self.sound_position,
|
sound_position=self.sound_position,
|
||||||
@@ -577,10 +572,31 @@ class SingleTrack(List[SingleNote]):
|
|||||||
return len(self)
|
return len(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def track_notes(self) -> List[SingleNote]:
|
def notes(self) -> List[SingleNote]:
|
||||||
"""音符列表"""
|
"""音符列表"""
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def minenotes(self) -> Iterator[MineNote]:
|
||||||
|
"""
|
||||||
|
直接返回当前音轨所有音符的我的世界数据形式
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
MineNote.from_single_note(
|
||||||
|
note=_note,
|
||||||
|
note_instrument=self.instrument,
|
||||||
|
is_persiced_time=self.is_high_time_precision,
|
||||||
|
is_percussive_note=self.is_percussive,
|
||||||
|
sound_position=self.sound_position,
|
||||||
|
**{
|
||||||
|
item.value: argcrv.value_at(_note.start_time)
|
||||||
|
for item in CurvableParam
|
||||||
|
if (argcrv := self.argument_curves[item])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for _note in self
|
||||||
|
)
|
||||||
|
|
||||||
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||||
"""设置附加信息"""
|
"""设置附加信息"""
|
||||||
if isinstance(key, str):
|
if isinstance(key, str):
|
||||||
@@ -621,18 +637,18 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
music_credits: str
|
music_credits: str
|
||||||
"""曲目的版权信息"""
|
"""曲目的版权信息"""
|
||||||
|
|
||||||
# 感叹一下什么交冗余设计啊!(叉腰)
|
# 感叹一下什么叫冗余设计啊!(叉腰)
|
||||||
extra_info: Dict[str, Any]
|
extra_info: Dict[str, Any]
|
||||||
"""这还得放东西?"""
|
"""这还得放东西?"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
*args: SingleTrack,
|
||||||
name: str = "未命名乐曲",
|
name: str = "未命名乐曲",
|
||||||
creator: str = "未命名制作者",
|
creator: str = "未命名制作者",
|
||||||
original_author: str = "未命名原作者",
|
original_author: str = "未命名原曲作",
|
||||||
description: str = "未命名简介",
|
description: str = "无简介",
|
||||||
credits: str = "未命名版权信息",
|
credits: str = "保留所有权利。All Rights Reserved.",
|
||||||
*args: SingleTrack,
|
|
||||||
extra_information: Dict[str, Any] = {},
|
extra_information: Dict[str, Any] = {},
|
||||||
):
|
):
|
||||||
self.music_name = name
|
self.music_name = name
|
||||||
@@ -687,7 +703,8 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
归并后的每个元素,按 sort_key 升序
|
归并后的每个元素,按 sort_key 升序
|
||||||
"""
|
"""
|
||||||
if is_subseq_sorted:
|
if is_subseq_sorted:
|
||||||
return heapq.merge(*tracks, key=sort_key)
|
# 必须这样处理,不能 return 这个 merge,测试过了
|
||||||
|
yield from heapq.merge(*tracks, key=sort_key)
|
||||||
else:
|
else:
|
||||||
# 初始化堆
|
# 初始化堆
|
||||||
heap_pool: List[Tuple[Any, int, T]] = []
|
heap_pool: List[Tuple[Any, int, T]] = []
|
||||||
@@ -755,11 +772,11 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
|
|
||||||
def get_minenotes(
|
def get_minenotes(
|
||||||
self, start_time: float, end_time: float = inf
|
self, start_time: float, end_time: float = inf
|
||||||
) -> Generator[MineNote, Any, None]:
|
) -> Iterator[MineNote]:
|
||||||
"""获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序"""
|
"""获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序"""
|
||||||
if self.track_amount == 0:
|
if self.track_amount == 0:
|
||||||
return
|
return iter(())
|
||||||
yield from self.yield_from_tracks(
|
return self.yield_from_tracks(
|
||||||
[track.get_minenotes(start_time, end_time) for track in self.music_tracks],
|
[track.get_minenotes(start_time, end_time) for track in self.music_tracks],
|
||||||
sort_key=lambda x: x.start_tick,
|
sort_key=lambda x: x.start_tick,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 用到的一些报错类型
|
音·创 v3 用到的一些报错类型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -106,10 +106,10 @@ class OuterlyParameterError(MusicreaterOuterlyError):
|
|||||||
|
|
||||||
|
|
||||||
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
|
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
|
||||||
"""以0作为播放速度的错误"""
|
"""以 0 作为播放速度的错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""以0作为播放速度的错误"""
|
"""以 0 作为播放速度的错误"""
|
||||||
super().__init__("播放速度为零:", *args)
|
super().__init__("播放速度为零:", *args)
|
||||||
|
|
||||||
|
|
||||||
@@ -225,12 +225,19 @@ class PluginLoadError(MusicreaterOuterlyError):
|
|||||||
super().__init__("插件加载错误 - ", *args)
|
super().__init__("插件加载错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginDependencyNotFound(PluginLoadError):
|
||||||
|
"""插件依赖未找到"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__("未找到所需的插件依赖:", *args)
|
||||||
|
|
||||||
|
|
||||||
class PluginNotFoundError(PluginLoadError):
|
class PluginNotFoundError(PluginLoadError):
|
||||||
"""插件未找到"""
|
"""插件未找到"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""插件未找到"""
|
"""插件未找到"""
|
||||||
super().__init__("插件未找到:", *args)
|
super().__init__("无法找到插件:", *args)
|
||||||
|
|
||||||
|
|
||||||
class PluginRegisteredError(PluginLoadError):
|
class PluginRegisteredError(PluginLoadError):
|
||||||
@@ -241,7 +248,6 @@ class PluginRegisteredError(PluginLoadError):
|
|||||||
super().__init__("插件重复注册:", *args)
|
super().__init__("插件重复注册:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PluginConfigRelatedError(MusicreaterOuterlyError):
|
class PluginConfigRelatedError(MusicreaterOuterlyError):
|
||||||
"""插件配置相关错误"""
|
"""插件配置相关错误"""
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
是一款免费开源的《我的世界》数字音频支持库。
|
是一款免费开源的《我的世界》数字音频支持库。
|
||||||
|
|
||||||
Musicreater (音·创)
|
Musicreater (音·创)
|
||||||
A free and open-source library for handling with **Minecraft** digital music.
|
A cost free and open-source library for handling with **Minecraft** digital music.
|
||||||
|
|
||||||
版权所有 © 2026 睿乐组织
|
版权所有 © 2026 睿乐组织
|
||||||
Copyright © 2026 TriM-Organization
|
Copyright © 2026 TriM-Organization
|
||||||
@@ -41,7 +41,7 @@ https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
|
|||||||
# Bug retreat! Bug retreat!
|
# Bug retreat! Bug retreat!
|
||||||
# Exceptions and errors are causing chaos
|
# Exceptions and errors are causing chaos
|
||||||
# Words combine! Codes unite!
|
# Words combine! Codes unite!
|
||||||
# Hurry to call the programmer! Let's Go!
|
# Hurry to call the Programmer! Let's Go!
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -96,9 +96,10 @@ class MusiCreater:
|
|||||||
def _get_plugin_within_iousage(
|
def _get_plugin_within_iousage(
|
||||||
get_func: Callable[[Union[Path, str]], Generator[T_IOPlugin, None, None]],
|
get_func: Callable[[Union[Path, str]], Generator[T_IOPlugin, None, None]],
|
||||||
fpath: Path,
|
fpath: Path,
|
||||||
plg_regdict: Dict[str, T_IOPlugin],
|
plg_regdict: Mapping[str, T_IOPlugin],
|
||||||
plg_id: Optional[str],
|
plg_id: Optional[str],
|
||||||
) -> T_IOPlugin:
|
) -> T_IOPlugin:
|
||||||
|
"""这个函数是用于从指定的注册表项里面调取实例的,仅供下面这几个函数使用"""
|
||||||
|
|
||||||
__plugin: Optional[T_IOPlugin] = None
|
__plugin: Optional[T_IOPlugin] = None
|
||||||
if plg_id:
|
if plg_id:
|
||||||
@@ -115,9 +116,16 @@ class MusiCreater:
|
|||||||
__plugin = __plg
|
__plugin = __plg
|
||||||
if __plugin:
|
if __plugin:
|
||||||
return __plugin
|
return __plugin
|
||||||
|
else:
|
||||||
|
if plg_id:
|
||||||
|
raise PluginNotFoundError(
|
||||||
|
"无法找到惟一识别码为`{}`、处理`{}`格式的插件".format(
|
||||||
|
plg_id, fpath.suffix.upper()
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise FileFormatNotSupportedError(
|
raise FileFormatNotSupportedError(
|
||||||
"无法找到处理`{}`类型文件的插件".format(fpath.suffix.upper())
|
"无法找到处理`{}`格式的插件".format(fpath.suffix.upper())
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -158,7 +166,7 @@ class MusiCreater:
|
|||||||
plugin_id: Optional[str] = None,
|
plugin_id: Optional[str] = None,
|
||||||
plugin_config: Optional[PluginConfig] = None,
|
plugin_config: Optional[PluginConfig] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._get_plugin_within_iousage(
|
return self._get_plugin_within_iousage(
|
||||||
self.__plugin_registry.get_music_output_plugin_by_format,
|
self.__plugin_registry.get_music_output_plugin_by_format,
|
||||||
file_path,
|
file_path,
|
||||||
self.__plugin_registry._music_output_plugins,
|
self.__plugin_registry._music_output_plugins,
|
||||||
@@ -172,7 +180,7 @@ class MusiCreater:
|
|||||||
plugin_id: Optional[str] = None,
|
plugin_id: Optional[str] = None,
|
||||||
plugin_config: Optional[PluginConfig] = None,
|
plugin_config: Optional[PluginConfig] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._get_plugin_within_iousage(
|
return self._get_plugin_within_iousage(
|
||||||
self.__plugin_registry.get_track_output_plugin_by_format,
|
self.__plugin_registry.get_track_output_plugin_by_format,
|
||||||
file_path,
|
file_path,
|
||||||
self.__plugin_registry._track_output_plugins,
|
self.__plugin_registry._track_output_plugins,
|
||||||
@@ -257,13 +265,11 @@ class MusiCreater:
|
|||||||
self._plugin_cache[plg_id] = self._plugin_cache[plugin_name]
|
self._plugin_cache[plg_id] = self._plugin_cache[plugin_name]
|
||||||
return self._plugin_cache[plg_id]
|
return self._plugin_cache[plg_id]
|
||||||
|
|
||||||
closest = self._get_closest_plugin_id(plg_id)
|
|
||||||
|
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"插件`{}`不存在,请检查插件的惟一识别码是否正确".format(plg_id)
|
"插件`{}`不存在,请检查插件的惟一识别码是否正确".format(plg_id)
|
||||||
+ (
|
+ (
|
||||||
";或者阁下可能想要使用的是`{}`插件?".format(closest)
|
";或者阁下可能想要使用的是`{}`插件?".format(closest)
|
||||||
if closest
|
if (closest := self._get_closest_plugin_id(plg_id))
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 内部数据使用的参数曲线
|
音·创 v3 内部数据使用的参数曲线
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -26,12 +26,10 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Any, List, Tuple
|
from typing import Optional, Any, List, Tuple, Callable
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import bisect
|
import bisect
|
||||||
|
|
||||||
from .types import FittingFunctionType
|
|
||||||
|
|
||||||
|
|
||||||
def _evaluate_bezier_segment(
|
def _evaluate_bezier_segment(
|
||||||
t0: float,
|
t0: float,
|
||||||
@@ -178,7 +176,7 @@ class Keyframe:
|
|||||||
value: float
|
value: float
|
||||||
|
|
||||||
# 函数插值模式
|
# 函数插值模式
|
||||||
out_interp: Optional[FittingFunctionType] = None
|
out_interp: Optional[Callable[[float], float]] = None
|
||||||
|
|
||||||
# 贝塞尔模式
|
# 贝塞尔模式
|
||||||
in_tangent: Optional[Tuple[float, float]] = (
|
in_tangent: Optional[Tuple[float, float]] = (
|
||||||
@@ -215,7 +213,7 @@ class ParamCurve:
|
|||||||
base_line: float = 0.0
|
base_line: float = 0.0
|
||||||
"""基线/默认值"""
|
"""基线/默认值"""
|
||||||
|
|
||||||
base_interpolation_function: FittingFunctionType
|
base_interpolation_function: Callable[[float], float]
|
||||||
"""默认(未指定区间时的)关键帧插值模式"""
|
"""默认(未指定区间时的)关键帧插值模式"""
|
||||||
|
|
||||||
boundary_behaviour: BoundaryBehaviour
|
boundary_behaviour: BoundaryBehaviour
|
||||||
@@ -227,7 +225,7 @@ class ParamCurve:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_value: float = 0.0,
|
base_value: float = 0.0,
|
||||||
default_interpolation_function: FittingFunctionType = InterpolationMethod.linear,
|
default_interpolation_function: Callable[[float], float] = InterpolationMethod.linear,
|
||||||
boundary_mode: BoundaryBehaviour = BoundaryBehaviour.CONSTANT,
|
boundary_mode: BoundaryBehaviour = BoundaryBehaviour.CONSTANT,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -257,7 +255,7 @@ class ParamCurve:
|
|||||||
self,
|
self,
|
||||||
time: float,
|
time: float,
|
||||||
value: float,
|
value: float,
|
||||||
out_interp: Optional[FittingFunctionType] = None,
|
out_interp: Optional[Callable[[float], float]] = None,
|
||||||
in_tangent: Optional[Tuple[float, float]] = None,
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
out_tangent: Optional[Tuple[float, float]] = None,
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
use_bezier: bool = False,
|
use_bezier: bool = False,
|
||||||
@@ -328,7 +326,7 @@ class ParamCurve:
|
|||||||
def update_key_interp(
|
def update_key_interp(
|
||||||
self,
|
self,
|
||||||
time: float,
|
time: float,
|
||||||
out_interp: Optional[FittingFunctionType] = None,
|
out_interp: Optional[Callable[[float], float]] = None,
|
||||||
in_tangent: Optional[Tuple[float, float]] = None,
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
out_tangent: Optional[Tuple[float, float]] = None,
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
use_bezier: bool = False,
|
use_bezier: bool = False,
|
||||||
@@ -486,7 +484,7 @@ class ParamCurve:
|
|||||||
"""返回 (time, value) 列表。"""
|
"""返回 (time, value) 列表。"""
|
||||||
return [(k.time, k.value) for k in self._keys]
|
return [(k.time, k.value) for k in self._keys]
|
||||||
|
|
||||||
def set_default_interpolation_function(self, interp_func: FittingFunctionType):
|
def set_default_interpolation_function(self, interp_func: Callable[[float], float]):
|
||||||
"""设置默认插值函数。"""
|
"""设置默认插值函数。"""
|
||||||
self.base_interpolation_function = interp_func
|
self.base_interpolation_function = interp_func
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 的插件接口与管理相关内容
|
音·创 v3 的插件接口与管理相关内容
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -33,6 +33,7 @@ from typing import (
|
|||||||
TypeVar,
|
TypeVar,
|
||||||
Mapping,
|
Mapping,
|
||||||
Callable,
|
Callable,
|
||||||
|
Type,
|
||||||
)
|
)
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ from .exceptions import (
|
|||||||
ParameterTypeError,
|
ParameterTypeError,
|
||||||
PluginInstanceNotFoundError,
|
PluginInstanceNotFoundError,
|
||||||
PluginRegisteredError,
|
PluginRegisteredError,
|
||||||
|
PluginNotFoundError,
|
||||||
|
PluginDependencyNotFound,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -119,6 +122,7 @@ def load_plugin_module(package: Union[Path, str]):
|
|||||||
插件包路径或名称,当为 Path 类时为路径,为 str 时为包名,切勿混淆。
|
插件包路径或名称,当为 Path 类时为路径,为 str 时为包名,切勿混淆。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
if isinstance(package, Path):
|
if isinstance(package, Path):
|
||||||
relative_path = package.resolve().relative_to(Path.cwd().resolve())
|
relative_path = package.resolve().relative_to(Path.cwd().resolve())
|
||||||
if relative_path.stem == "__init__":
|
if relative_path.stem == "__init__":
|
||||||
@@ -129,6 +133,8 @@ def load_plugin_module(package: Union[Path, str]):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return importlib.import_module(package)
|
return importlib.import_module(package)
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise PluginNotFoundError("无法找到名为`{}`的插件包".format(package)) from e
|
||||||
|
|
||||||
|
|
||||||
class PluginRegistry:
|
class PluginRegistry:
|
||||||
@@ -143,6 +149,7 @@ class PluginRegistry:
|
|||||||
self._track_output_plugins: Dict[str, TrackOutputPluginBase] = {}
|
self._track_output_plugins: Dict[str, TrackOutputPluginBase] = {}
|
||||||
self._service_plugins: Dict[str, ServicePluginBase] = {}
|
self._service_plugins: Dict[str, ServicePluginBase] = {}
|
||||||
self._library_plugins: Dict[str, LibraryPluginBase] = {}
|
self._library_plugins: Dict[str, LibraryPluginBase] = {}
|
||||||
|
self._all_plugin_id: List = []
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[Tuple[PluginTypes, Mapping[str, TopPluginBase]]]:
|
def __iter__(self) -> Iterator[Tuple[PluginTypes, Mapping[str, TopPluginBase]]]:
|
||||||
"""迭代器,返回所有插件"""
|
"""迭代器,返回所有插件"""
|
||||||
@@ -159,23 +166,29 @@ class PluginRegistry:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
def _register_plugin(
|
||||||
def _register_plugin(cls_dict: dict, plg_class: type, plg_id: str) -> None:
|
self, cls_dict: dict, plg_class: Type[TopPluginBase], plg_id: str
|
||||||
|
) -> None:
|
||||||
"""注册插件"""
|
"""注册插件"""
|
||||||
if plg_id in cls_dict:
|
if plg_id in cls_dict:
|
||||||
if cls_dict[plg_id].metainfo.version >= plg_class.metainfo.version:
|
if cls_dict[plg_id].metainfo.version >= plg_class.metainfo.version:
|
||||||
raise PluginRegisteredError(
|
raise PluginRegisteredError(
|
||||||
"插件惟一识别码`{}`所对应的插件已存在更高版本`{}`,请勿重复注册同一插件!".format(
|
"插件惟一识别码`{}`所对应的插件已存在更高版本`{}`,请勿重复注册同一插件。".format(
|
||||||
plg_id, plg_class.metainfo
|
plg_id, plg_class.metainfo
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if missing_requirements := [
|
||||||
|
i for i in plg_class.metainfo.dependencies if i not in self._all_plugin_id
|
||||||
|
]:
|
||||||
|
raise PluginDependencyNotFound(
|
||||||
|
"插件 `{}` 依赖于这些插件:`{}`;当前环境中缺失:`{}`。加载此插件时,请务必将被依赖的插件提前载入。".format(
|
||||||
|
plg_id, plg_class.metainfo.dependencies, missing_requirements
|
||||||
|
)
|
||||||
|
)
|
||||||
cls_dict[plg_id] = plg_class()
|
cls_dict[plg_id] = plg_class()
|
||||||
|
self._all_plugin_id.append(plg_id)
|
||||||
|
|
||||||
def register_music_input_plugin(
|
def register_music_input_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
self,
|
|
||||||
plugin_class: type,
|
|
||||||
plugin_id: str,
|
|
||||||
) -> None:
|
|
||||||
"""注册输入插件-整首曲目"""
|
"""注册输入插件-整首曲目"""
|
||||||
self._register_plugin(self._music_input_plugins, plugin_class, plugin_id)
|
self._register_plugin(self._music_input_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
@@ -209,7 +222,7 @@ class PluginRegistry:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_io_plugin_by_format(
|
def _get_io_plugin_by_format(
|
||||||
plugin_regdict: Dict[str, T_IOPlugin], fpath_or_format: Union[Path, str]
|
plugin_regdict: Mapping[str, T_IOPlugin], fpath_or_format: Union[Path, str]
|
||||||
) -> Generator[T_IOPlugin, None, None]:
|
) -> Generator[T_IOPlugin, None, None]:
|
||||||
if isinstance(fpath_or_format, str):
|
if isinstance(fpath_or_format, str):
|
||||||
return (
|
return (
|
||||||
@@ -218,6 +231,7 @@ class PluginRegistry:
|
|||||||
if plugin.can_handle_format(fpath_or_format)
|
if plugin.can_handle_format(fpath_or_format)
|
||||||
)
|
)
|
||||||
elif isinstance(fpath_or_format, Path):
|
elif isinstance(fpath_or_format, Path):
|
||||||
|
# print("在",plugin_regdict,"中,查找可用于处理",fpath_or_format,"的插件")
|
||||||
return (
|
return (
|
||||||
plugin
|
plugin
|
||||||
for plugin in plugin_regdict.values()
|
for plugin in plugin_regdict.values()
|
||||||
@@ -278,10 +292,10 @@ class PluginRegistry:
|
|||||||
],
|
],
|
||||||
key=lambda plugin: plugin.metainfo.version,
|
key=lambda plugin: plugin.metainfo.version,
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError as e:
|
||||||
raise PluginInstanceNotFoundError(
|
raise PluginInstanceNotFoundError(
|
||||||
"未找到“用于{}、名为`{}`”的插件".format(plugin_usage, plugin_name)
|
"未找到“用于{}、名为`{}`”的插件".format(plugin_usage, plugin_name)
|
||||||
)
|
) from e
|
||||||
|
|
||||||
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPluginBase:
|
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPluginBase:
|
||||||
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
|
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
|
||||||
@@ -389,6 +403,7 @@ def music_operate_plugin(plugin_id: str):
|
|||||||
plugin_id, _global_plugin_registry.register_music_operate_plugin
|
plugin_id, _global_plugin_registry.register_music_operate_plugin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def track_operate_plugin(plugin_id: str):
|
def track_operate_plugin(plugin_id: str):
|
||||||
"""音轨处理插件装饰器"""
|
"""音轨处理插件装饰器"""
|
||||||
return __plugin_regist_decorator(
|
return __plugin_regist_decorator(
|
||||||
@@ -416,6 +431,7 @@ def service_plugin(plugin_id: str):
|
|||||||
plugin_id, _global_plugin_registry.register_service_plugin
|
plugin_id, _global_plugin_registry.register_service_plugin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def library_plugin(plugin_id: str):
|
def library_plugin(plugin_id: str):
|
||||||
"""支持库插件装饰器"""
|
"""支持库插件装饰器"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 定义的一些数据类型,可以用于类型检查器
|
音·创 v3 定义的一些数据类型,可以用于类型检查器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -19,7 +19,3 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
|
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
|
||||||
|
|
||||||
FittingFunctionType = Callable[[float], float]
|
|
||||||
"""
|
|
||||||
拟合函数类型
|
|
||||||
"""
|
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,9 +1,12 @@
|
|||||||
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
|
<!-- [Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge&label=作者B站
|
||||||
[Bilibili: 玉衡Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-00A1E7?style=for-the-badge
|
[Bilibili: 玉衡Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-00A1E7?style=for-the-badge&label=作者B站 -->
|
||||||
[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&label=代码风格
|
||||||
[python]: https://img.shields.io/badge/python-3.8-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/TriM-Organization/Musicreater?style=for-the-badge&label=发行版
|
||||||
[license]: https://img.shields.io/badge/Licence-%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE-228B22?style=for-the-badge
|
[license]: https://img.shields.io/badge/Licence-%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE-228B22?style=for-the-badge&label=协议
|
||||||
|
[commit-activity]: https://img.shields.io/github/commit-activity/m/TriM-Organization/Musicreater%2Fmaster?style=for-the-badge&label=提交活动&color=AB70FF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h1 align="center">音·创 Musicreater </h1>
|
<h1 align="center">音·创 Musicreater </h1>
|
||||||
|
|
||||||
@@ -22,8 +25,8 @@
|
|||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
[![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
|
<!-- [![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
|
||||||
[![][Bilibili: 玉衡Alioth]](https://space.bilibili.com/604072474)
|
[![][Bilibili: 玉衡Alioth]](https://space.bilibili.com/604072474) -->
|
||||||
[![CodeStyle: black]](https://github.com/psf/black)
|
[![CodeStyle: black]](https://github.com/psf/black)
|
||||||
[![][python]](https://www.python.org/)
|
[![][python]](https://www.python.org/)
|
||||||
[![][license]](LICENSE)
|
[![][license]](LICENSE)
|
||||||
@@ -105,6 +108,7 @@
|
|||||||
- 感谢 **雨**\<QQ237667809\> 反馈在新版本的指令格式下,计分板播放器的附加包无法播放的问题。
|
- 感谢 **雨**\<QQ237667809\> 反馈在新版本的指令格式下,计分板播放器的附加包无法播放的问题。
|
||||||
- 感谢 **梦幻duang**\<QQ13753593\> 为我们提供 Java 1.12.2 版本命令格式参考。
|
- 感谢 **梦幻duang**\<QQ13753593\> 为我们提供 Java 1.12.2 版本命令格式参考。
|
||||||
- 感谢 [_Open Note Block Studio_](https://github.com/OpenNBS/NoteBlockStudio) 项目的开发为我们提供持续的追赶动力。
|
- 感谢 [_Open Note Block Studio_](https://github.com/OpenNBS/NoteBlockStudio) 项目的开发为我们提供持续的追赶动力。
|
||||||
|
- 感谢 **启航与凡凡**\<QQ2777856500\> 找到 **音·创 v2** 版本音符序列文件解码的错误并指出修正方式。
|
||||||
|
|
||||||
> 感谢广大群友为此库提供的测试和建议等
|
> 感谢广大群友为此库提供的测试和建议等
|
||||||
> 若您对我们有所贡献但您的名字没有出现在此列表中,请联系我们!
|
> 若您对我们有所贡献但您的名字没有出现在此列表中,请联系我们!
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</img>
|
</img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 align="center">A free and open-source library for handling with <i>Minecraft</i> digital music.</h3>
|
<h3 align="center">A cost free and open-source library for handling with <i>Minecraft</i> digital music.</h3>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
||||||
|
|||||||
41
TO-DO.md
41
TO-DO.md
@@ -6,31 +6,31 @@
|
|||||||
1. 使用 `.MCT` 作为项目文件的后缀,然后考虑一下格式是否和之前的 MusicSequence 兼容,如果兼容的话可以照旧用 `.MSQ`,如不的话,可以试试想一个新的后缀名作为数据文件后缀
|
1. 使用 `.MCT` 作为项目文件的后缀,然后考虑一下格式是否和之前的 MusicSequence 兼容,如果兼容的话可以照旧用 `.MSQ`,如不的话,可以试试想一个新的后缀名作为数据文件后缀
|
||||||
2. 要求数据文件支持完全流式读入
|
2. 要求数据文件支持完全流式读入
|
||||||
|
|
||||||
- 音轨静音处理
|
- [] 音轨静音处理
|
||||||
当前没有处理
|
当前没有处理
|
||||||
|
|
||||||
- 优化音轨的存储方式
|
- [] 优化音轨的存储方式
|
||||||
当前是用列表,且每一次变动元素都要重新排序,这样消耗太大了,需要优化,改用最小堆形式(heapq)
|
当前是用列表,且每一次变动元素都要重新排序,这样消耗太大了,需要优化,改用最小堆形式(heapq)
|
||||||
|
|
||||||
- 移植 v2 功能到内置插件
|
- 移植 v2 功能到内置插件
|
||||||
目前 v2 的功能有很多,都要移植到 v3。
|
目前 v2 的功能有很多,都要移植到 v3。
|
||||||
1. 导入 Midi 文件到全曲
|
1. [x] 导入 Midi 文件到全曲
|
||||||
2. 导入 Midi 文件到指定轨道
|
2. [] 导入 Midi 文件到指定轨道
|
||||||
3. 导出到延迟播放器的结构文件(MCSTRUCTURE、BDX)
|
3. [x] 导出到延迟播放器的结构文件(MCSTRUCTURE、BDX)
|
||||||
4. 导出到延迟播放器的附加包
|
4. [] 导出到延迟播放器的附加包
|
||||||
5. 导出到积分板播放器的以上两种形式
|
5. [] 导出到积分板播放器的以上两种形式
|
||||||
6. 导出到中继器播放器的以上两种形式
|
6. [] 导出到中继器播放器的以上两种形式
|
||||||
7. 在 WebSocket 播放器中播放
|
7. [] 在 WebSocket 播放器中播放
|
||||||
8. 导出到支持神羽资源包的以上 7 种形式
|
8. [] 导出到支持神羽资源包的以上 7 种形式
|
||||||
9. 对于 Midi 歌词的实验性功能
|
9. [] 对于 Midi 歌词的实验性功能
|
||||||
10. 对于 Java 版本适配的实验性功能
|
10. [] 对于 Java 版本适配的实验性功能
|
||||||
11. 对于听感优化的实验性功能(插值、偏移)
|
11. [] 对于听感优化的实验性功能(插值、偏移)
|
||||||
|
|
||||||
- 测试参数曲线的功能
|
- [] 测试参数曲线的功能
|
||||||
|
|
||||||
- 支持导出音符盒构成的音乐
|
- [] 支持导出音符盒构成的音乐
|
||||||
|
|
||||||
- 支持导出成 schematic 结构
|
- [] 支持导出成 schematic 结构
|
||||||
|
|
||||||
## 讨论
|
## 讨论
|
||||||
|
|
||||||
@@ -41,8 +41,13 @@
|
|||||||
|
|
||||||
引入了插件惟一识别码之后当然是采用 `Dict[插件唯一识别码, 插件对象]` 来存储插件了~之前插件名称的内容是我想得太浅了,我写完所有代码之后才想到插件名称是中文还带空格的任意字符串……
|
引入了插件惟一识别码之后当然是采用 `Dict[插件唯一识别码, 插件对象]` 来存储插件了~之前插件名称的内容是我想得太浅了,我写完所有代码之后才想到插件名称是中文还带空格的任意字符串……
|
||||||
|
|
||||||
2. 服务插件到底该怎么写?总不能留着一个 PluginType.SERVICE 的插件一直空在那里吧……
|
2. [] 服务插件到底该怎么写?总不能留着一个 PluginType.SERVICE 的插件一直空在那里吧……
|
||||||
|
|
||||||
3. 插件依赖性的优化。目前没有处理各个插件依赖关系的问题,如果插件之间彼此依赖要怎么做?
|
3. [x] 插件依赖性的优化。目前没有处理各个插件依赖关系的问题,如果插件之间彼此依赖要怎么做?
|
||||||
我的想法是,这个依赖的处理由调用端来完成。比如我们的 伶伦工作站 是以 音·创 为核心的一个可视化数字音频工作站。
|
我的想法是,这个依赖的处理由调用端来完成。比如我们的 伶伦工作站 是以 音·创 为核心的一个可视化数字音频工作站。
|
||||||
那么应该由伶伦来处理依赖关系并加载之。
|
那么应该由伶伦来处理依赖关系并加载之。
|
||||||
|
|
||||||
|
**当前已经大致解决**
|
||||||
|
|
||||||
|
首先有一个验证顺序,我们在插件加载后会验证,当前已加载的插件中是否包括了所需的插件,如果缺少则报错。
|
||||||
|
这样的加载顺序安排仍然需要调用端来实现。
|
||||||
|
|||||||
@@ -6,8 +6,4 @@
|
|||||||
|
|
||||||
**此为开发相关文档,内容包括:库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释**
|
**此为开发相关文档,内容包括:库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释**
|
||||||
|
|
||||||
# [main.py](../Musicreater/main.py)
|
音·创 v3 的文档还在编纂过程中,请耐心等待。
|
||||||
|
|
||||||
## [类] MidiConvert
|
|
||||||
|
|
||||||
### [类函数] from_midi_file
|
|
||||||
@@ -1,315 +0,0 @@
|
|||||||
<h1 align="center">音·创 Musicreater</h1>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
|
|
||||||
</p>
|
|
||||||
|
|
||||||
**此为开发相关文档,内容包括:库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释**
|
|
||||||
|
|
||||||
# 库的简单调用
|
|
||||||
|
|
||||||
参见[example.py的相关部分](../example.py),使用此库进行MIDI转换非常简单。
|
|
||||||
|
|
||||||
- 在导入转换库后,使用 MidiConvert 类建立转换对象(读取Midi文件)
|
|
||||||
|
|
||||||
音·创库支持新旧两种execute语法,需要在对象实例化时指定
|
|
||||||
```python
|
|
||||||
# 导入音·创库
|
|
||||||
import Musicreater
|
|
||||||
|
|
||||||
# 指定是否使用旧的execute指令语法(即1.18及以前的《我的世界:基岩版》语法)
|
|
||||||
old_execute_format = False
|
|
||||||
|
|
||||||
# 可以通过文件地址自动读取
|
|
||||||
cvt_mid = Musicreater.MidiConvert.from_midi_file(
|
|
||||||
"Midi文件地址",
|
|
||||||
old_exe_format=old_execute_format
|
|
||||||
)
|
|
||||||
|
|
||||||
# 也可以导入Mido对象
|
|
||||||
cvt_mid = Musicreater.MidiConvert(
|
|
||||||
mido.MidiFile("Midi文件地址"),
|
|
||||||
"音乐名称",
|
|
||||||
old_exe_format=old_execute_format
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
- 获取 Midi 音乐经转换后的播放指令
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 通过函数 to_command_list_in_score, to_command_list_in_delay
|
|
||||||
# 分别可以得到
|
|
||||||
# 以计分板作为播放器的指令对象列表、以延迟作为播放器的指令对象列表
|
|
||||||
# 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
|
|
||||||
|
|
||||||
# 使用 to_command_list_in_score 函数进行转换之后,返回值有三个
|
|
||||||
# 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表
|
|
||||||
# 也就是列表套列表
|
|
||||||
# 但是,在对象内部所存储的数据却不会如此嵌套
|
|
||||||
command_channel_list, command_count, max_score = cvt_mid.to_command_list_in_score(
|
|
||||||
"计分板名称",
|
|
||||||
1.0, # 音量比率
|
|
||||||
1.0, # 速度倍率
|
|
||||||
)
|
|
||||||
|
|
||||||
# 使用 to_command_list_in_delay 转换后的返回值只有两个
|
|
||||||
# 但是第一个返回值没有列表套列表
|
|
||||||
command_list, max_delay = cvt_mid.to_command_list_in_delay(
|
|
||||||
1.0, # 音量比率
|
|
||||||
1.0, # 速度倍率
|
|
||||||
"@a", # 玩家选择器
|
|
||||||
)
|
|
||||||
|
|
||||||
# 运行之后,指令和总延迟会存储至对象内
|
|
||||||
print(
|
|
||||||
"音乐长度:{}/游戏刻".format(
|
|
||||||
cvt_mid.music_tick_num
|
|
||||||
)
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
"指令如下:\n{}".format(
|
|
||||||
cvt_mid.music_command_list
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
- 除了获取播放指令外,还可以获取进度条指令
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 通过函数 form_progress_bar 可以获得
|
|
||||||
# 以计分板为载体所生成的进度条的指令对象列表
|
|
||||||
# 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
|
|
||||||
|
|
||||||
# 使用 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, # 进度条样式组(详见下方)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 使用附加组件转换,其调用的函数应为:
|
|
||||||
# Musicreater.plugin.输出格式.播放器格式
|
|
||||||
# 值得注意的是,并非所有输出格式都支持所有播放器格式
|
|
||||||
# 调用的时候还请注意甄别
|
|
||||||
# 例如,以下函数是将 MidiConvert 对象 cvt_mid
|
|
||||||
# 以 cvt_cfg 指定的参数
|
|
||||||
# 以延迟播放器转换为 mcstructure 文件
|
|
||||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
|
||||||
cvt_mid,
|
|
||||||
cvt_cfg,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
# 生成文件结构
|
|
||||||
|
|
||||||
## 名词解释
|
|
||||||
|
|
||||||
|名词|解释|备注|
|
|
||||||
|--------|-----------|----------|
|
|
||||||
|指令区|一个用于放置指令系统的区域,通常是常加载区。|常见于服务器指令系统、好友联机房间中|
|
|
||||||
|指令链(链)|与链式指令方块不同,一个指令链通常指代的是一串由某种非链式指令方块作为开头,后面连着一串链式指令方块的结构。|通常的链都应用于需要“单次激活而多指令”的简单功能|
|
|
||||||
|起始块|链最初的那个非链式指令方块。|此方块为脉冲方块或重复方块皆可|
|
|
||||||
|指令系统(系统)|指令系统通常指的是,由一个或多个指令链以及相关红石机构相互配合、一同组成的,为达到某种特定的功能而构建的整体结构。|通常的系统都应用于需要“综合调配指令”的复杂功能。可由多个实现不同功能的模块构成,不同系统之间可以相互调用各自的模块。|
|
|
||||||
|游戏刻(刻)|游戏的一刻是指《我的世界》的游戏进程循环运行一次所占用的时间。([详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E6%B8%B8%E6%88%8F%E5%88%BB))。指令方块的延迟功能(即指令方块的“延迟刻数”设置项,此项的名称被误译为“已选中项的延迟”)的单位即为`1`游戏刻。|正常情况下,游戏固定以每秒钟 $20$ 刻的速率运行。但是,由于游戏内的绝大多数操作都是基于游戏进程循环而非现实中的时间来计时并进行的,一次游戏循环内也许会发生大量的操作,更多情况下,一秒对应的游戏刻会更少。|
|
|
||||||
|红石刻|一个红石刻代表了两个游戏刻。([详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E7%BA%A2%E7%9F%B3%E5%88%BB))。红石中继器会带来 $1$~$4$ 个红石刻的延迟,其默认的延迟时间为 $1$ 红石刻。|正常情况下,红石信号在一个红石电路中传输回存在 $\frac{1}{10}$ 秒左右的延迟。但是,同理于游戏刻,一秒对应的红石刻是不定的。|
|
|
||||||
|
|
||||||
## 播放器
|
|
||||||
|
|
||||||
**音·创**生成的文件可以采用多种方式播放,一类播放方式,我们称其为**播放器**,例如**延迟播放器**和**计分板播放器**等等,以后推出的新的播放器,届时也会在此处更新。
|
|
||||||
|
|
||||||
为什么要设计这么多播放器?是为了适应不同的播放环境需要。通常情况下,一个音乐中含有多个音符,音符与音符之间存在间隔,这里就产生了不一样的,实现音符间时间间隔的方式。而不同的应用环境下,又会产生不一样的要求。接下来将对不同的播放器进行详细介绍。
|
|
||||||
|
|
||||||
### 参数释义
|
|
||||||
|
|
||||||
|参数|说明|备注|
|
|
||||||
|--------|-----------|----------|
|
|
||||||
|`ScBd`|指定的计分板名称||
|
|
||||||
|`Tg`|播放对象|选择器或玩家名|
|
|
||||||
|`x`|音发出时对应的分数值||
|
|
||||||
|`InstID`|声音效果ID|不同的声音ID可以对应不同的乐器,详见转换[乐器对照表](./%E8%BD%AC%E6%8D%A2%E4%B9%90%E5%99%A8%E5%AF%B9%E7%85%A7%E8%A1%A8.md)|
|
|
||||||
|`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以 $S$ 表示此参数`Ht`,以Vol表示音量百分比,则计算公式为: $S = \frac{1}{Vol}-1$ |
|
|
||||||
|`Vlct`|原生我的世界中规定的播放力度|这个参数是一个谜一样的存在,似乎它的值毫不重要……因为无论这个值是多少,我们听起来都差不多。当此音符所在MIDI通道为第一通道,则这个值为 $0.7$ 倍MIDI指定力度,其他则为 $0.9$ 倍。|
|
|
||||||
|`Ptc`|音符的音高|这是决定音调的参数。以 $P$ 表示此参数, $n$ 表示其在MIDI中的编号, $x$ 表示一定的音调偏移,则计算公式为: $P = 2^\frac{n-60-x}{12}$。之所以存在音调偏移是因为在《我的世界》中,不同的[乐器存在不同的音域](https://zh.minecraft.wiki/wiki/%E9%9F%B3%E7%AC%A6%E7%9B%92#%E4%B9%90%E5%99%A8),我们通过音调偏移来进行调整。|
|
|
||||||
|
|
||||||
### 播放器内容
|
|
||||||
|
|
||||||
1. 计分板播放器
|
|
||||||
|
|
||||||
计分板播放器是一种传统的《我的世界》音乐播放方式。通过对于计分板加分来实现播放不同的音符。一个很简单的原理,就是**用不同的计分板分值对应不同的音符**,再通过加分,来达到那个分值,即播放出来。
|
|
||||||
|
|
||||||
在**音·创**中,用来达到这种效果的指令是这样的:
|
|
||||||
|
|
||||||
```mcfunction
|
|
||||||
execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
后四个参数决定了这个音的性质,而前两个参数仅仅是为了决定音播放的时间。
|
|
||||||
|
|
||||||
2. 延迟播放器
|
|
||||||
|
|
||||||
延迟播放器是通过《我的世界》游戏中,指令方块的设置项“延迟刻数”来达到定位音符的效果。**将所有的音符依照其播放时距离乐曲开始时的时间(毫秒),放在一个序列内,再计算音符两两之间对应的时间差值,转换为《我的世界》内对应的游戏刻数之后填入指令方块的设置中。**
|
|
||||||
|
|
||||||
在**音·创**中,由于此方式播放的音乐不需要用计分板,所以播放指令是这样的:
|
|
||||||
|
|
||||||
```mcfunction
|
|
||||||
execute Tg ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
其中后四个参数决定了这个音的性质。
|
|
||||||
|
|
||||||
由于这样的延迟数据是依赖于指令方块的设置项,所以使用这种播放器所转换出的结果仅可以存储在包含方块NBT信息及方块实体NBT信息的结构文件中,或者直接输出至世界。
|
|
||||||
|
|
||||||
3. 中继器播放器
|
|
||||||
|
|
||||||
中继器播放器是一种传统的《我的世界》红石音乐播放方式,利用游戏内“红石组件”之“红石中继器”以达到定位音符之用。**但是,中继器的延迟为1红石刻**
|
|
||||||
|
|
||||||
|
|
||||||
## 文件格式
|
|
||||||
|
|
||||||
1. 附加包格式(`.mcpack`)
|
|
||||||
|
|
||||||
使用附加包格式导出音乐,若采用计分板 播放器,则音乐会以指令函数文件(`.mcfunction`)存储于附加包内。而若为延迟或中继器播放器,则音乐回以结构文件(`.mcstructure`)存储。在所生成的附加包中,函数文件的存储结构应为:
|
|
||||||
|
|
||||||
- `functions\`
|
|
||||||
- `index.mcfunction`
|
|
||||||
- `stop.mcfunction`
|
|
||||||
- `mscply\`
|
|
||||||
- `progressShow.mcfunction`
|
|
||||||
- `track1.mcfunction`
|
|
||||||
- `track2.mcfunction`
|
|
||||||
- ...
|
|
||||||
- `trackN.mcfunction`
|
|
||||||
- `structures\`
|
|
||||||
- `XXX_main.mcstructure`
|
|
||||||
- `XXX_start.mcstructure`
|
|
||||||
- `XXX_reset.mcstructure`
|
|
||||||
- `XXX_pgb.mcstructure`
|
|
||||||
|
|
||||||
如图,其中,`index.mcfunction`文件、`stop.mcfunction`文件和`mscply`文件夹存在于函数目录的根下;在`mscply`目录中,包含音乐导出的众多音轨播放文件(`trackX.mcfunction`)。同时,若使用计分板播放器生成此包时启用生成进度条,则会包含`progressShow.mcfunction`文件。若选择延迟或中继器播放器,则会生成`structures`目录以及相关`.mcstructure`文件,其中`mian`表示音乐播放用的主要结构;`start`是用于初始化播放的部分,仅包含一个指令方块即起始块;`reset`和`pgb`仅在启用生成进度条时出现,前者用于重置临时计分板,后者用于显示进度条。
|
|
||||||
|
|
||||||
`index.mcfunction`用于开始播放:
|
|
||||||
|
|
||||||
1. 若为计分板播放器,则其中包含打开各个音轨对应函数的指令,以及加分指令,这里的加分,是将**播放计分板的值大于等于 $1$ 的所有玩家**的播放计分板分数增加 $1$。同时,若生成此包时选择了自动重置计分板的选项,则会包含一条重置计分板的指令。
|
|
||||||
|
|
||||||
2. 若为延迟或中继器播放器,则其中的指令仅包含用以正确加载结构的`structure`指令。
|
|
||||||
|
|
||||||
`stop.mcfunction`用于终止播放:
|
|
||||||
|
|
||||||
1. 若为计分板播放器,则其中包含将**全体玩家的播放计分板**重置的指令。
|
|
||||||
|
|
||||||
2. 若为延迟或中继器播放器,则其中包含**停用命令方块**和**启用命令方块**的指令。~~然鹅实际上对于播放而言是一点用也没有~~
|
|
||||||
|
|
||||||
> 你知道吗?音·创的最早期版本“《我的世界》函数音乐生成器”正是用函数来播放,不过这个版本采取的读入数据的形式大有不同。
|
|
||||||
|
|
||||||
2. 生成结构的方式
|
|
||||||
|
|
||||||
无论是音·创生成的是何种结构,`MCSTRUCTURE`还是`BDX`,都会依照此处的格式来生成。此处我们想说明的结构的格式不是结构文件存储的格式,而是结构导出之后方块摆放的方式。结构文件存储的格式这一点,在各个《我的世界》开发的相关网站上都可能会有说明。
|
|
||||||
|
|
||||||
考虑到进行《我的世界》游戏开发时,为了节约常加载区域,很多游戏会将指令区设立为一种层叠式的结构。这种结构会限制每一层的指令系统的高度,但是虽然长宽也是有限的,却仍然比其纵轴延伸得更加自由。
|
|
||||||
|
|
||||||
所以,结构的生成形状依照给定的高度和内含指令的数量决定。其 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整。用数学公式的方式表达,则是:
|
|
||||||
|
|
||||||
$$ MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor $$
|
|
||||||
|
|
||||||
其中,$MaxZ$ 即生成结构的$Z$轴最大延伸长度,$NoC$ 表示链结构中所含指令方块的个数,$MaxH$ 表示给定的生成结构的最大高度。
|
|
||||||
|
|
||||||
我们的结构生成器在生成指令链时,将首先以相对坐标系 $(0, 0, 0)$ (即相对原点)开始,自下向上堆叠高度轴(即 $Y$ 轴)的长,当高度轴达到了限制的高度时,便将 $Z$ 轴向正方向堆叠 $1$ 个方块,并开始自上向下重新堆叠,直至高度轴坐标达到相对为 $0$。若当所生成结构的 $Z$ 轴长达到了其最大延伸长度,则此结构生成器将反转 $Z$ 轴的堆叠方向,直至 $Z$ 轴坐标相对为 $0$。如此往复,直至指令链堆叠完成。
|
|
||||||
|
|
||||||
|
|
||||||
# 进度条自定义
|
|
||||||
|
|
||||||
因为我们提供了可以自动转换进度条的功能,因此在这里给出进度条自定义参数的详细解释。
|
|
||||||
|
|
||||||
一个进度条,明显地,有**固定部分**和**可变部分**来构成。而可变部分又包括了文字和图形两种(当然,《我的世界》里头的进度条,可变的图形也就是那个“条”了)。这一点你需要了解,因为后文中包含了很多这方面的概念需要你了解。
|
|
||||||
|
|
||||||
进度条的自定义功能使用一个字符串来定义自己的样式,其中包含众多**标识符**来表示可变部分。
|
|
||||||
|
|
||||||
标识符如下(注意大小写):
|
|
||||||
|
|
||||||
| 标识符 | 指定的可变量 |
|
|
||||||
|---------|----------------|
|
|
||||||
| `%%N` | 乐曲名(即传入的文件名)|
|
|
||||||
| `%%s` | 当前计分板值 |
|
|
||||||
| `%^s` | 计分板最大值 |
|
|
||||||
| `%%t` | 当前播放时间 |
|
|
||||||
| `%^t` | 曲目总时长 |
|
|
||||||
| `%%%` | 当前进度比率 |
|
|
||||||
| `_` | 用以表示进度条占位|
|
|
||||||
|
|
||||||
表示进度条占位的 `_` 是用来标识你的进度条的。也就是可变部分的唯一的图形部分。
|
|
||||||
|
|
||||||
**样式定义字符串(基础样式)**的样例如下,这也是默认进度条的基础样式:
|
|
||||||
|
|
||||||
```▶ %%N [ %%s/%^s %%% __________ %%t|%^t]```
|
|
||||||
|
|
||||||
这是单独一行的进度条,当然你也可以制作多行的,如果是一行的,输出时所使用的指令便是 `title`,而如果是多行的话,输出就会用 `titleraw` 作为进度条字幕。
|
|
||||||
|
|
||||||
哦对了,上面的只不过是样式定义,同时还需要定义的是可变图形的部分,也就是进度条上那个真正的“条”。
|
|
||||||
|
|
||||||
对于这个我们就采用了固定参数的方法,对于一个进度条,无非就是“已经播放过的”和“没播放过的”两种形态,例如,我们默认的进度“条”(**可变样式**)的定义是这样的:
|
|
||||||
|
|
||||||
**可变样式甲(已播放样式)**:`'§e=§r'`
|
|
||||||
|
|
||||||
**可变样式乙(未播放样式)**:`'§7=§r'`
|
|
||||||
|
|
||||||
综合起来,把这些参数传给函数需要一个参数整合,使用位于 `Musicreater/subclass.py` 下的 `ProgressBarStyle` 类进行定义:
|
|
||||||
|
|
||||||
我们的默认定义参数如下:
|
|
||||||
|
|
||||||
```python
|
|
||||||
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
|
|
||||||
r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]",
|
|
||||||
r"§e=§r",
|
|
||||||
r"§7=§r",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
*为了避免生成错误,请尽量避免使用标识符作为定义样式字符串的其他部分*
|
|
||||||
|
|
||||||
237
docs/异常继承关系.mmd
Normal file
237
docs/异常继承关系.mmd
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
|
||||||
|
classDiagram
|
||||||
|
|
||||||
|
direction LR
|
||||||
|
|
||||||
|
class Exception{
|
||||||
|
Python 内置基类
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicreaterBaseException {
|
||||||
|
"[音·创] - ..."
|
||||||
|
所有音·创 v3 错误的基类
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicreaterInnerlyError {
|
||||||
|
"内部错误 - ..."
|
||||||
|
面向开发者的内部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicreaterOuterlyError {
|
||||||
|
"外部错误 - ..."
|
||||||
|
面向用户的外部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class InnerlyParameterError {
|
||||||
|
"内部传参错误 - ..."
|
||||||
|
内部参数相关错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class OuterlyParameterError {
|
||||||
|
"参数错误 - ..."
|
||||||
|
外部参数相关错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParameterTypeError {
|
||||||
|
"参数类型错误:..."
|
||||||
|
继承自 InnerlyParameterError 和 TypeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParameterValueError {
|
||||||
|
"参数数值错误:..."
|
||||||
|
继承自 InnerlyParameterError 和 ValueError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginNotSpecifiedError {
|
||||||
|
"未指定插件:..."
|
||||||
|
继承自 InnerlyParameterError 和 LookupError
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZeroSpeedError {
|
||||||
|
"播放速度为零:..."
|
||||||
|
继承自 OuterlyParameterError 和 ZeroDivisionError
|
||||||
|
}
|
||||||
|
|
||||||
|
class IllegalMinimumVolumeError {
|
||||||
|
"最小播放音量超出范围:..."
|
||||||
|
继承自 OuterlyParameterError 和 ValueError
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileFormatNotSupportedError {
|
||||||
|
"不支持的文件格式:..."
|
||||||
|
继承自 MusicreaterOuterlyError
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteBinaryDecodeError {
|
||||||
|
"解码音乐存储二进制数据时出现问题 - ..."
|
||||||
|
继承自 MusicreaterOuterlyError
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleNoteDecodeError {
|
||||||
|
"音符解码出错:..."
|
||||||
|
继承自 NoteBinaryDecodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteBinaryFileTypeError {
|
||||||
|
"无法识别音乐存储文件对应的类型:..."
|
||||||
|
继承自 NoteBinaryDecodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteBinaryFileVerificationFailed {
|
||||||
|
"音乐存储文件校验失败:..."
|
||||||
|
继承自 NoteBinaryDecodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginDefineError {
|
||||||
|
"插件内部错误 - ..."
|
||||||
|
插件定义相关的内部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginInstanceNotFoundError {
|
||||||
|
"插件实例未找到:..."
|
||||||
|
继承自 PluginDefineError 和 LookupError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginAttributeNotFoundError {
|
||||||
|
"插件类的必要属性不存在:..."
|
||||||
|
继承自 PluginDefineError 和 AttributeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoError {
|
||||||
|
"插件元信息定义错误 - ..."
|
||||||
|
插件元信息相关错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoTypeError {
|
||||||
|
"插件元信息类型错误:..."
|
||||||
|
继承自 PluginMetainfoError 和 TypeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoValueError {
|
||||||
|
"插件元信息数值错误:..."
|
||||||
|
继承自 PluginMetainfoError 和 ValueError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoNotFoundError {
|
||||||
|
"插件元信息未定义:..."
|
||||||
|
继承自 PluginMetainfoError 和 PluginAttributeNotFoundError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginLoadError {
|
||||||
|
"插件加载错误 - ..."
|
||||||
|
插件加载相关的外部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginNotFoundError {
|
||||||
|
"插件未找到:..."
|
||||||
|
继承自 PluginLoadError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginRegisteredError {
|
||||||
|
"插件重复注册:..."
|
||||||
|
继承自 PluginLoadError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginConfigRelatedError {
|
||||||
|
"插件配置相关错误 - ..."
|
||||||
|
插件配置相关错误基类
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginConfigLoadError {
|
||||||
|
"插件配置文件加载错误:..."
|
||||||
|
继承自 PluginLoadError、PluginConfigRelatedError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginConfigDumpError {
|
||||||
|
"插件配置文件保存错误:..."
|
||||||
|
继承自 PluginConfigRelatedError
|
||||||
|
}
|
||||||
|
%% 高亮定义
|
||||||
|
|
||||||
|
class ParameterTypeError ::: highlight
|
||||||
|
class ParameterValueError ::: highlight
|
||||||
|
class PluginNotSpecifiedError ::: highlight
|
||||||
|
class ZeroSpeedError ::: highlight
|
||||||
|
class IllegalMinimumVolumeError ::: highlight
|
||||||
|
class FileFormatNotSupportedError ::: highlight
|
||||||
|
class SingleNoteDecodeError ::: highlight
|
||||||
|
class NoteBinaryFileTypeError ::: highlight
|
||||||
|
class NoteBinaryFileVerificationFailed ::: highlight
|
||||||
|
class PluginInstanceNotFoundError ::: highlight
|
||||||
|
class PluginAttributeNotFoundError ::: highlight
|
||||||
|
class PluginMetainfoTypeError ::: highlight
|
||||||
|
class PluginMetainfoValueError ::: highlight
|
||||||
|
class PluginMetainfoNotFoundError ::: highlight
|
||||||
|
class PluginNotFoundError ::: highlight
|
||||||
|
class PluginRegisteredError ::: highlight
|
||||||
|
class PluginConfigLoadError ::: highlight
|
||||||
|
class PluginConfigDumpError ::: highlight
|
||||||
|
|
||||||
|
%% 定义高亮样式
|
||||||
|
classDef highlight fill:,stroke-width:5px
|
||||||
|
|
||||||
|
%% 继承关系(箭头从子类指向父类)
|
||||||
|
Exception <|-- MusicreaterBaseException
|
||||||
|
Exception <|-- TypeError
|
||||||
|
Exception <|-- ValueError
|
||||||
|
Exception <|-- LookupError
|
||||||
|
Exception <|-- AttributeError
|
||||||
|
Exception <|-- ZeroDivisionError
|
||||||
|
MusicreaterBaseException <|-- MusicreaterInnerlyError
|
||||||
|
MusicreaterBaseException <|-- MusicreaterOuterlyError
|
||||||
|
|
||||||
|
MusicreaterInnerlyError <|-- InnerlyParameterError
|
||||||
|
MusicreaterOuterlyError <|-- OuterlyParameterError
|
||||||
|
|
||||||
|
InnerlyParameterError <|-- ParameterTypeError
|
||||||
|
TypeError <|-- ParameterTypeError
|
||||||
|
|
||||||
|
InnerlyParameterError <|-- ParameterValueError
|
||||||
|
ValueError <|-- ParameterValueError
|
||||||
|
|
||||||
|
InnerlyParameterError <|-- PluginNotSpecifiedError
|
||||||
|
LookupError <|-- PluginNotSpecifiedError
|
||||||
|
|
||||||
|
OuterlyParameterError <|-- ZeroSpeedError
|
||||||
|
ZeroDivisionError <|-- ZeroSpeedError
|
||||||
|
|
||||||
|
OuterlyParameterError <|-- IllegalMinimumVolumeError
|
||||||
|
ValueError <|-- IllegalMinimumVolumeError
|
||||||
|
|
||||||
|
MusicreaterOuterlyError <|-- FileFormatNotSupportedError
|
||||||
|
MusicreaterOuterlyError <|-- NoteBinaryDecodeError
|
||||||
|
|
||||||
|
NoteBinaryDecodeError <|-- SingleNoteDecodeError
|
||||||
|
NoteBinaryDecodeError <|-- NoteBinaryFileTypeError
|
||||||
|
NoteBinaryDecodeError <|-- NoteBinaryFileVerificationFailed
|
||||||
|
|
||||||
|
MusicreaterInnerlyError <|-- PluginDefineError
|
||||||
|
|
||||||
|
PluginDefineError <|-- PluginInstanceNotFoundError
|
||||||
|
LookupError <|-- PluginInstanceNotFoundError
|
||||||
|
|
||||||
|
PluginDefineError <|-- PluginAttributeNotFoundError
|
||||||
|
AttributeError <|-- PluginAttributeNotFoundError
|
||||||
|
|
||||||
|
PluginDefineError <|-- PluginMetainfoError
|
||||||
|
|
||||||
|
PluginMetainfoError <|-- PluginMetainfoTypeError
|
||||||
|
TypeError <|-- PluginMetainfoTypeError
|
||||||
|
|
||||||
|
PluginMetainfoError <|-- PluginMetainfoValueError
|
||||||
|
ValueError <|-- PluginMetainfoValueError
|
||||||
|
|
||||||
|
PluginMetainfoError <|-- PluginMetainfoNotFoundError
|
||||||
|
PluginAttributeNotFoundError <|-- PluginMetainfoNotFoundError
|
||||||
|
|
||||||
|
MusicreaterOuterlyError <|-- PluginLoadError
|
||||||
|
|
||||||
|
PluginLoadError <|-- PluginNotFoundError
|
||||||
|
PluginLoadError <|-- PluginRegisteredError
|
||||||
|
|
||||||
|
MusicreaterOuterlyError <|-- PluginConfigRelatedError
|
||||||
|
|
||||||
|
PluginLoadError <|-- PluginConfigLoadError
|
||||||
|
PluginConfigRelatedError <|-- PluginConfigLoadError
|
||||||
|
|
||||||
|
PluginConfigRelatedError <|-- PluginConfigDumpError
|
||||||
67
docs/异常继承关系.svg
Normal file
67
docs/异常继承关系.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 652 KiB |
@@ -1,56 +0,0 @@
|
|||||||
<h1 align="center">音·创 Musicreater</h1>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
|
|
||||||
</p>
|
|
||||||
|
|
||||||
# 生成文件的使用
|
|
||||||
|
|
||||||
*这是本库所生成文件的使用声明,不是使用本库的教程,若要查看**本库的文档**,可点击[此处](./%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`
|
|
||||||
|
|
||||||
- 计分板播放器
|
|
||||||
|
|
||||||
1. 导入附加包
|
|
||||||
2. 在一个循环方块中输入指令 `function index`
|
|
||||||
3. 将需要聆听音乐的实体的播放所用计分板设置为 `1`
|
|
||||||
4. 激活循环方块
|
|
||||||
5. 若想要暂停播放,可以停止循环指令方块的激活状态
|
|
||||||
6. 若想要重置某实体的播放,可以将其播放用的计分板重置
|
|
||||||
7. 若要终止全部玩家的播放,在聊天框输入指令 `function stop`
|
|
||||||
|
|
||||||
> 其中 步骤三 和 步骤四 的顺序可以调换。
|
|
||||||
|
|
||||||
- 延迟播放器
|
|
||||||
|
|
||||||
1. 导入附加包
|
|
||||||
2. 在聊天框输入指令 `function index`
|
|
||||||
3. 同时激活所生成的循环和脉冲指令方块
|
|
||||||
4. 若要终止播放,在聊天框输入指令 `function stop` 试试看,不确保有用
|
|
||||||
|
|
||||||
> 需要注意的是,循环指令方块需要一直激活直到音乐结束
|
|
||||||
|
|
||||||
## 结构格式
|
|
||||||
|
|
||||||
支持的文件后缀:`.MCSTRUCTURE`、`.BDX`
|
|
||||||
|
|
||||||
1. 将结构导入世界
|
|
||||||
|
|
||||||
- 延迟播放器
|
|
||||||
|
|
||||||
2. 将结构生成的第一个指令方块之模式更改为**脉冲**
|
|
||||||
3. 激活脉冲方块
|
|
||||||
4. 若欲重置播放,可以停止对此链的激活,例如停止区块加载
|
|
||||||
5. 此播放器不支持暂停
|
|
||||||
|
|
||||||
- 计分板播放器
|
|
||||||
|
|
||||||
2. 在所生成的第一个指令方块前,放置一个循环指令方块,其朝向应当对着所生成的第一个方块
|
|
||||||
3. 在循环指令方块中输入使播放对象的播放用计分板加分的指令,延迟为 `0`,每次循环增加 `1` 分
|
|
||||||
4. 激活循环方块
|
|
||||||
5. 若想要暂停播放,可以停止循环指令方块的激活状态
|
|
||||||
6. 若想要重置某实体的播放,可以将其播放用的计分板重置
|
|
||||||
|
|
||||||
185
docs/编写插件.md
Normal file
185
docs/编写插件.md
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
|
||||||
|
# 教程:编写插件
|
||||||
|
|
||||||
|
> 版权所有 © 2026 金羿
|
||||||
|
> Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
睿乐组织 开发交流群 [861684859](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=fxNYIX_zKMgaO8X6K7pP7tHtLB7JRvdX&noverify=0&group_code=861684859)
|
||||||
|
Email [TriM-Organization@hotmail.com](mailto:TriM-Organization@hotmail.com)
|
||||||
|
|
||||||
|
```license
|
||||||
|
本示例模块开放授权,同时,本教程文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
本声明同样适用于所有直接转载的内容。
|
||||||
|
```
|
||||||
|
|
||||||
|
本教程文档的关联文件是:
|
||||||
|
- 全曲导入、音轨导入插件示例:[exp_importdata_plugin.py](../examples/exp_importdata_plugin.py)
|
||||||
|
- 导出曲目、导出音轨插件示例:[exp_dataexport_plugin.py](../examples/exp_dataexport_plugin.py)
|
||||||
|
|
||||||
|
## 新建文件
|
||||||
|
|
||||||
|
### 基础模块知识
|
||||||
|
|
||||||
|
首先,一个 **音·创 v3** 的插件应当存储于一个 Python 模块之中,也就是插件存在于可以被 import 语句引入的 module 中。
|
||||||
|
|
||||||
|
这就意味着,承载插件的模块本质上可以是多个 Python 的 `.py` 文件组成的,带有 `__init__.py` 的一个文件夹;
|
||||||
|
或者是一个简单的 `.py` 文件。
|
||||||
|
|
||||||
|
我们有这种共识:大家已经知道了模块的相关知识,后面的教程中你将会理解 **音·创 v3** 插件是什么东西,以及它和 Python 模块的关联和区别。
|
||||||
|
|
||||||
|
## 开始动笔
|
||||||
|
|
||||||
|
### 插件配置
|
||||||
|
|
||||||
|
如果插件需要配置项,则需进行此节。
|
||||||
|
|
||||||
|
从 `Musicreater.plugins` 导入 `PluginConfig` 类,并从此继承一个类,且须用 dataclass 装饰器来注册之:这就成为了一个插件的配置类。
|
||||||
|
_对于这个 `dataclass` “数据类”的使用方式,可以阅读 dataclass 的官方文档,或者直接在实例后面打个 `.`,让代码提示告诉你它能干什么_
|
||||||
|
|
||||||
|
```python
|
||||||
|
from Musicreater.plugins import PluginConfig
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExampleImportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 编写插件
|
||||||
|
|
||||||
|
### 导入所需项目
|
||||||
|
|
||||||
|
首先在代码开头导入插件所需的东西。
|
||||||
|
|
||||||
|
在此之前,我们明确:一个 **音·创 v3** 的插件应当是一个继承自我们已经准备好的插件基类的**类**(缩句:插件是类);
|
||||||
|
在 **音·创 v3** 中,任何对音乐的操作,包括**导入**、**处理**、**导出**,都分为对 **整首曲目** 的操作和对 **单个音轨** 的操作。
|
||||||
|
|
||||||
|
在这里我们首先要对插件的类型进行判别,根据以下表格,可以得出所用功能对应的插件类型:
|
||||||
|
|
||||||
|
| 功能\对象 | 完整曲目 | 单个音轨 |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| 导入数据 | `PluginTypes.FUNCTION_MUSIC_IMPORT` | `PluginTypes.FUNCTION_TRACK_IMPORT` |
|
||||||
|
| 数据处理 | `PluginTypes.FUNCTION_MUSIC_OPERATE` | `PluginTypes.FUNCTION_TRACK_OPERATE` |
|
||||||
|
| 导出数据 | `PluginTypes.FUNCTION_MUSIC_EXPORT` | `PluginTypes.FUNCTION_TRACK_EXPORT` |
|
||||||
|
| 支持库 | `PluginTypes.LIBRARY` | 同左 |
|
||||||
|
| 提供服务 | `PluginTypes.SERVICE` | 同左 |
|
||||||
|
|
||||||
|
也就是说,除了 `PluginTypes.LIBRARY` 和 `PluginTypes.SERVICE` 是不按照处理对象做区分的外,其余的这些都是对数据进行处理的插件、因此是做了处理数据的类型区分的。
|
||||||
|
|
||||||
|
我们对每个不同类型的插件都做了专用的抽象基类和一个装饰器函数。因为插件本身就是类,所以对应类型的插件只需要继承我们提供的抽象基类,并通过装饰器函数注册即可。(具体写法在后面会说哦)
|
||||||
|
|
||||||
|
也就是说,如果我们要写的是一个用来导入音乐的、对整个曲目进行处理的插件,那么就需要导入 `MusicInputPluginBase` 类和 `music_input_plugin` 函数以便后续调用。
|
||||||
|
|
||||||
|
同时,既然要导入内容,那就一并把 `PluginMetaInformation` 类和 `PluginTypes` 类也导入了吧,这是定义插件的信息所需要的。也就是说,这样的话,我们在导入部分就应该这样写:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
music_input_plugin,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
MusicInputPluginBase,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 定义信息
|
||||||
|
|
||||||
|
接着我们来定义一个插件的信息并将其注册。
|
||||||
|
|
||||||
|
假设我们想要做一个对**整首曲目**进行**导入操作**的插件(参照前面举的例子),那么就需要继承 `MusicInputPluginBase` 类。
|
||||||
|
|
||||||
|
> 请注意:插件类的类名称不得以 `Base` 结尾,因为咱写的是插件,不是插件基类。
|
||||||
|
|
||||||
|
在插件的类的开头,需要用插件注册装饰函数来对插件类装饰。
|
||||||
|
|
||||||
|
```python
|
||||||
|
@music_input_plugin("example_import_plugin")
|
||||||
|
class xxx:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
我们这里对应插件类型的注册器是 `music_input_plugin` 函数。
|
||||||
|
在注册器函数后的参数,是这个插件的惟一识别码。不应与其他任何插件混淆。
|
||||||
|
通常,这个惟一识别码可以是这个插件的功能描述或者就是插件名。
|
||||||
|
|
||||||
|
接着编写这个插件,也即是此类。
|
||||||
|
|
||||||
|
每个插件的类必须包含一个用于指定插件元信息的 `metainfo` 属性。
|
||||||
|
如果插件是导入数据或者导出数据的插件,则必须包含一个 `supported_formats` 属性,用以声明插件所支持的数据格式。
|
||||||
|
|
||||||
|
对于插件的元信息,我们规定为一个 `PluginMetaInformation` 实例,这个实例需要的参数如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 注册插件
|
||||||
|
@music_input_plugin("something_convert_to_music")
|
||||||
|
# 继承自对应类型的插件基类
|
||||||
|
class ExampleImportPlugin(MusicInputPluginBase):
|
||||||
|
|
||||||
|
# 插件元信息定义
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件", # 插件名称
|
||||||
|
author="金羿", # 插件作者
|
||||||
|
description="这是一个示例导入插件", # 插件描述
|
||||||
|
version=(0, 0, 1), # 插件版本
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT, # 插件类型,需要和注册的类型与继承的基类相符合
|
||||||
|
license="The Unlicense", # 插件许可证(可缺省,默认为字符串 `MIT License`)
|
||||||
|
dependencies=("something_convertion_library") # 插件对于其他插件的依赖项(可缺省,默认为空元组)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
对于实现导入导出数据的功能的插件,`supported_formats` 属性应当是一个元组,其中最好以全字母大写的字符串形式列出支持的**文件格式**或者**数据格式**(如果定义的时候没有大写的话,内部会自动处理成大写的,所以插件类的实例后面也会变成大写,这个时候,因为原定义是小写,有可能造成混淆,所以尽量不要写小写)。例如一个处理 `.mp4` 文件格式的插件可以这样写:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@...
|
||||||
|
class ...:
|
||||||
|
...
|
||||||
|
|
||||||
|
supported_formats = ("MP4", "MPEG4", "MPEG-4")
|
||||||
|
```
|
||||||
|
|
||||||
|
至此,你已经完成了插件基本信息的定义。
|
||||||
|
|
||||||
|
### 实现功能
|
||||||
|
|
||||||
|
根据插件的类型不同,每个插件都需要实现至少一个指定的方法。如下表所示:
|
||||||
|
|
||||||
|
| 插件功能 | 必须实现的方法 | 类型描述 | 可选实现的方法| 可选方法类型描述 | 备注 |
|
||||||
|
| ------ | ------------ | - | ----------- | - |----|
|
||||||
|
| 导入数据 | `loadbytes` | `Callable[[BinaryIO, Optional[PluginConfig]], T@插件处理对象类型]` | `load` | `Callable[[Path, Optional[PluginConfig]], T@插件处理对象类型]` | 如果 `load` 方法不单独实现,则会自动在打开文件后将文件 IO 变量传入 `loadbytes` 中并返回之 |
|
||||||
|
| 数据处理 | `process` | `Callable[[T@插件处理对象类型, Optional[PluginConfig]], T@插件处理对象类型]` | 无 | 无 | 根据处理对象是完整曲目(`SingleMusic`)还是单个音轨(`SingleTrack`),返回也是一样的。导入导出数据相关的插件亦皆同此说。 |
|
||||||
|
| 导出数据 | `stream_dump` | `Callable[[T@插件处理对象类型, Optional[PluginConfig]], Iterator[bytes]]` | `dump` | `Callable[[T@插件处理对象类型, Path, Optional[PluginConfig]], None]` | 若未重写 `dump` 方法,基类已提供默认实现:逐块写入 `stream_dump` 的结果 |
|
||||||
|
| 支持库 | 无 | 无 | 无 | 无 | 无 |
|
||||||
|
| 服务 | `serve` | `Callable[[Optional[PluginConfig]], None]` | 无 | 无 | 用于提供后台服务或一次性任务,由运行时调用(暂无设计思路,相关讨论请见[项目待办清单](../TO-DO.md#讨论)) |
|
||||||
|
|
||||||
|
也就是说,举个例子:一个**用于导入**的插件类必须定义一个 `loadbytes` 方法,用于从字节流中导入数据。可选是否单独实现 `load` 方法,如果不单独实现,则已经继承的方法会在调用时,直接通过打开文件后传参数给 `loadbytes` 来实现。
|
||||||
|
|
||||||
|
```python
|
||||||
|
@...
|
||||||
|
class ExampleImportPlugin(MusicInputPluginBase):
|
||||||
|
...
|
||||||
|
# 定义 loadbytes 方法,从字节流中导入数据
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
... # 这里写功能实现
|
||||||
|
|
||||||
|
# 插件可选地定义 load 方法,从文件导入数据。下面展示的是不定义 load 方法时候的实现方式
|
||||||
|
def load(
|
||||||
|
self, file_path: Path, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return self.loadbytes(f, config)
|
||||||
|
```
|
||||||
|
|
||||||
|
至此,一个插件的编写已经完成。
|
||||||
|
|
||||||
|
同时,如果有不清楚的地方,可以查看我们的[内置插件](../Musicreater/builtin_plugins/),说不定会给你一些启发。
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# 音乐序列文件格式
|
# 音乐序列文件格式(已过时)
|
||||||
|
|
||||||
音·创 库的音符序列文件格式包含两种,一种是常规的音乐序列存储采用的 MSQ 格式,另一种是为了流式读取音符而采用的 FSQ 格式。
|
音·创 库的音符序列文件格式包含两种,一种是常规的音乐序列存储采用的 MSQ 格式,另一种是为了流式读取音符而采用的 FSQ 格式。
|
||||||
|
|
||||||
|
|||||||
113
examples/exp_dataexport_plugin.py
Normal file
113
examples/exp_dataexport_plugin.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
示例插件:导出成其他文件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
本示例模块开放授权,本文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
本声明同样适用于所有直接转载的内容。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
music_output_plugin,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
track_output_plugin,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExampleExportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
|
||||||
|
def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
|
||||||
|
for k, v in self.to_dict().items():
|
||||||
|
yield k, v
|
||||||
|
|
||||||
|
|
||||||
|
@music_output_plugin("convert_music_to_something")
|
||||||
|
class ExampleExportMusicPlugin(MusicOutputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导出插件·甲",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导出插件,演示整首曲目导出到其他文件格式的插件的编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_EXPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
dependencies=("something_convertion_library"),
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "EXAMPLE_FORMAT")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def something_data_convert(*args) -> bytes:
|
||||||
|
return b"This is something wonderful"
|
||||||
|
|
||||||
|
def stream_dump(
|
||||||
|
self, data: SingleMusic, config: ExampleExportConfig | None
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
if not config:
|
||||||
|
config = ExampleExportConfig(True)
|
||||||
|
for cfg in config:
|
||||||
|
yield self.something_data_convert(cfg)
|
||||||
|
|
||||||
|
# 插件可选地定义 dump 方法,从文件导入数据。下面展示的是不定义 load 方法时候的实现方式
|
||||||
|
def dump(
|
||||||
|
self, data: SingleMusic, file_path: Path, config: ExampleExportConfig | None
|
||||||
|
):
|
||||||
|
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
|
@track_output_plugin("convert_track_to_something")
|
||||||
|
class ExampleImportTrackPlugin(TrackOutputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导出插件·乙",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导出插件,演示从音轨导出的其他格式的插件的编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_TRACK_EXPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
# 可以缺省依赖,如果不需要的话
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "example_format")
|
||||||
|
|
||||||
|
def stream_dump(
|
||||||
|
self, data: SingleTrack, config: ExampleExportConfig | None
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
if not config:
|
||||||
|
config = ExampleExportConfig(True)
|
||||||
|
for cfg in config:
|
||||||
|
yield ExampleExportMusicPlugin.something_data_convert(cfg)
|
||||||
|
|
||||||
|
# 可以缺省 dump 方法,会直接用上面展示过的方法输出
|
||||||
97
examples/exp_importdata_plugin.py
Normal file
97
examples/exp_importdata_plugin.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
示例插件:导入音符数据
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
本示例模块开放授权,本文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
本声明同样适用于所有直接转载的内容。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import BinaryIO, Optional
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
music_input_plugin,
|
||||||
|
MusicInputPluginBase,
|
||||||
|
track_input_plugin,
|
||||||
|
TrackInputPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExampleImportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@music_input_plugin("something_convert_to_music")
|
||||||
|
class ExampleImportMusicPlugin(MusicInputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件·甲",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导入插件,演示导入到全曲的插件编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
dependencies=("something_convertion_library"),
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "EXAMPLE_FORMAT")
|
||||||
|
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
return SingleMusic()
|
||||||
|
|
||||||
|
# 插件可选地定义 load 方法,从文件导入数据。下面展示的是不定义 load 方法时候的实现方式
|
||||||
|
def load(
|
||||||
|
self, file_path: Path, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return self.loadbytes(f, config)
|
||||||
|
|
||||||
|
|
||||||
|
@track_input_plugin("something_convert_to_track")
|
||||||
|
class ExampleImportTrackPlugin(TrackInputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件·乙",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导入插件,演示导入到音轨的插件编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_TRACK_IMPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
# 可以缺省依赖,如果不需要的话
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "example_format")
|
||||||
|
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleTrack":
|
||||||
|
return SingleTrack()
|
||||||
|
|
||||||
|
# 可以缺省 load 方法,会直接用上面展示过的方法读取数据
|
||||||
@@ -28,10 +28,10 @@ from .old_main import (
|
|||||||
mido,
|
mido,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .constants import MIDI_PAN, MIDI_PROGRAM, MIDI_VOLUME
|
from .old_main import MIDI_PAN, MIDI_PROGRAM, MIDI_VOLUME
|
||||||
from .subclass import *
|
from .subclass import *
|
||||||
from .old_types import ChannelType, FittingFunctionType
|
from .old_types import ChannelType, FittingFunctionType
|
||||||
from .utils import *
|
from .old_utils import *
|
||||||
|
|
||||||
|
|
||||||
class FutureMidiConvertLyricSupport(MidiConvert):
|
class FutureMidiConvertLyricSupport(MidiConvert):
|
||||||
@@ -106,7 +106,7 @@ class FutureMidiConvertLyricSupport(MidiConvert):
|
|||||||
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tick_delay=tickdelay,
|
delay=tickdelay,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if using_lyric and note.extra_info["LYRIC_TEXT"]:
|
if using_lyric and note.extra_info["LYRIC_TEXT"]:
|
||||||
@@ -182,10 +182,10 @@ class FutureMidiConvertKamiRES(MidiConvert):
|
|||||||
raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。")
|
raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。")
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
midi_channels: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
midi_channels: MineNoteChannelType = enumerated_stuff_copy(staff=[])
|
||||||
|
|
||||||
channel_controler: Dict[int, Dict[str, int]] = empty_midi_channels(
|
channel_controler: Dict[int, Dict[str, int]] = enumerated_stuff_copy(
|
||||||
default_staff={
|
staff={
|
||||||
MIDI_PROGRAM: default_program_value,
|
MIDI_PROGRAM: default_program_value,
|
||||||
MIDI_VOLUME: default_volume_value,
|
MIDI_VOLUME: default_volume_value,
|
||||||
MIDI_PAN: 64,
|
MIDI_PAN: 64,
|
||||||
@@ -205,7 +205,7 @@ class FutureMidiConvertKamiRES(MidiConvert):
|
|||||||
int,
|
int,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
] = empty_midi_channels(default_staff=[])
|
] = enumerated_stuff_copy(staff=[])
|
||||||
note_queue_B: Dict[
|
note_queue_B: Dict[
|
||||||
int,
|
int,
|
||||||
List[
|
List[
|
||||||
@@ -214,7 +214,7 @@ class FutureMidiConvertKamiRES(MidiConvert):
|
|||||||
int,
|
int,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
] = empty_midi_channels(default_staff=[])
|
] = enumerated_stuff_copy(staff=[])
|
||||||
|
|
||||||
# 直接使用mido.midifiles.tracks.merge_tracks转为单轨
|
# 直接使用mido.midifiles.tracks.merge_tracks转为单轨
|
||||||
# 采用的时遍历信息思路
|
# 采用的时遍历信息思路
|
||||||
@@ -469,7 +469,7 @@ class FutureMidiConvertKamiRES(MidiConvert):
|
|||||||
mc_sound_ID,
|
mc_sound_ID,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tick_delay=tickdelay,
|
delay=tickdelay,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
delaytime_previous = note.start_tick
|
delaytime_previous = note.start_tick
|
||||||
@@ -501,7 +501,7 @@ class FutureMidiConvertJavaE(MidiConvert):
|
|||||||
-------
|
-------
|
||||||
list[MineCommand,]
|
list[MineCommand,]
|
||||||
"""
|
"""
|
||||||
pgs_style = progressbar_style.base_style
|
pgs_style = progressbar_style.style_base_string
|
||||||
"""用于被替换的进度条原始样式"""
|
"""用于被替换的进度条原始样式"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -686,8 +686,8 @@ class FutureMidiConvertJavaE(MidiConvert):
|
|||||||
|
|
||||||
for i in range(pgs_style.count("_")):
|
for i in range(pgs_style.count("_")):
|
||||||
npg_stl = (
|
npg_stl = (
|
||||||
pgs_style.replace("_", progressbar_style.played_style, i + 1)
|
pgs_style.replace("_", progressbar_style.progress_played, i + 1)
|
||||||
.replace("_", progressbar_style.to_play_style)
|
.replace("_", progressbar_style.progress_toplay)
|
||||||
.replace(r"%%N", self.music_name)
|
.replace(r"%%N", self.music_name)
|
||||||
.replace(
|
.replace(
|
||||||
r"%%s",
|
r"%%s",
|
||||||
@@ -1006,7 +1006,7 @@ class FutureMidiConvertM4(MidiConvert):
|
|||||||
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tick_delay=tickdelay,
|
delay=tickdelay,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
delaytime_previous = note.start_tick
|
delaytime_previous = note.start_tick
|
||||||
@@ -1042,7 +1042,7 @@ class FutureMidiConvertM5(MidiConvert):
|
|||||||
# )
|
# )
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
midi_channels: ChannelType = empty_midi_channels()
|
midi_channels: ChannelType = enumerated_stuff_copy()
|
||||||
tempo = 500000
|
tempo = 500000
|
||||||
|
|
||||||
# 我们来用通道统计音乐信息
|
# 我们来用通道统计音乐信息
|
||||||
@@ -1052,7 +1052,7 @@ class FutureMidiConvertM5(MidiConvert):
|
|||||||
if not track:
|
if not track:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
note_queue = empty_midi_channels(default_staff=[])
|
note_queue = enumerated_stuff_copy(staff=[])
|
||||||
|
|
||||||
for msg in track:
|
for msg in track:
|
||||||
if msg.time != 0:
|
if msg.time != 0:
|
||||||
@@ -1197,7 +1197,7 @@ class FutureMidiConvertM5(MidiConvert):
|
|||||||
results.append(
|
results.append(
|
||||||
MineCommand(
|
MineCommand(
|
||||||
tracks[all_ticks[i]][j],
|
tracks[all_ticks[i]][j],
|
||||||
tick_delay=(
|
delay=(
|
||||||
(
|
(
|
||||||
0
|
0
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -34,14 +34,6 @@ class MSCTBaseException(Exception):
|
|||||||
raise self
|
raise self
|
||||||
|
|
||||||
|
|
||||||
class MidiFormatException(MSCTBaseException):
|
|
||||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
|
||||||
super().__init__("MIDI 格式错误", *args)
|
|
||||||
|
|
||||||
|
|
||||||
class MidiDestroyedError(MSCTBaseException):
|
class MidiDestroyedError(MSCTBaseException):
|
||||||
"""Midi文件损坏"""
|
"""Midi文件损坏"""
|
||||||
|
|
||||||
@@ -82,84 +74,3 @@ class CommandFormatError(MSCTBaseException, RuntimeError):
|
|||||||
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
||||||
|
|
||||||
|
|
||||||
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设定导致没有乐器可以选择的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
|
||||||
super().__init__("未指定演奏乐器", *args)
|
|
||||||
|
|
||||||
|
|
||||||
class NoteOnOffMismatchError(MidiFormatException):
|
|
||||||
"""音符开音和停止不匹配的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""音符开音和停止不匹配的错误"""
|
|
||||||
super().__init__("音符不匹配", *args)
|
|
||||||
|
|
||||||
|
|
||||||
class LyricMismatchError(MSCTBaseException):
|
|
||||||
"""歌词匹配解析错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""有可能产生了错误的歌词解析"""
|
|
||||||
super().__init__("歌词解析错误", *args)
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
|
|
||||||
"""以0作为播放速度的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""以0作为播放速度的错误"""
|
|
||||||
super().__init__("播放速度为零", *args)
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class IllegalMinimumVolumeError(MSCTBaseException, ValueError):
|
|
||||||
"""最小播放音量有误的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""最小播放音量错误"""
|
|
||||||
super().__init__("最小播放音量超出范围", *args)
|
|
||||||
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class MusicSequenceDecodeError(MSCTBaseException):
|
|
||||||
"""音乐序列解码错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""音乐序列无法正确解码的错误"""
|
|
||||||
super().__init__("解码音符序列文件时出现问题", *args)
|
|
||||||
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class MusicSequenceTypeError(MSCTBaseException):
|
|
||||||
"""音乐序列类型错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""无法识别音符序列字节码的类型"""
|
|
||||||
super().__init__("错误的音符序列字节类型", *args)
|
|
||||||
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class MusicSequenceVerificationFailed(MusicSequenceDecodeError):
|
|
||||||
"""音乐序列校验失败"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""音符序列文件与其校验值不一致"""
|
|
||||||
super().__init__("音符序列文件校验失败", *args)
|
|
||||||
|
|||||||
@@ -85,7 +85,14 @@ __all__ = [
|
|||||||
"midi_inst_to_mc_sound",
|
"midi_inst_to_mc_sound",
|
||||||
]
|
]
|
||||||
|
|
||||||
from .old_main import MusicSequence, MidiConvert
|
from .old_main import (
|
||||||
|
MusicSequence,
|
||||||
|
MidiConvert,
|
||||||
|
# 字典键
|
||||||
|
MIDI_PROGRAM,
|
||||||
|
MIDI_PAN,
|
||||||
|
MIDI_VOLUME,
|
||||||
|
)
|
||||||
|
|
||||||
from .subclass import (
|
from .subclass import (
|
||||||
MineNote,
|
MineNote,
|
||||||
@@ -96,7 +103,7 @@ from .subclass import (
|
|||||||
DEFAULT_PROGRESSBAR_STYLE,
|
DEFAULT_PROGRESSBAR_STYLE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .utils import (
|
from .old_utils import (
|
||||||
# 兼容性函数
|
# 兼容性函数
|
||||||
load_decode_musicsequence_metainfo,
|
load_decode_musicsequence_metainfo,
|
||||||
load_decode_msq_flush_release,
|
load_decode_msq_flush_release,
|
||||||
@@ -104,21 +111,26 @@ from .utils import (
|
|||||||
# 工具函数
|
# 工具函数
|
||||||
guess_deviation,
|
guess_deviation,
|
||||||
midi_inst_to_mc_sound,
|
midi_inst_to_mc_sound,
|
||||||
# 处理用函数
|
|
||||||
velocity_2_distance_natural,
|
|
||||||
velocity_2_distance_straight,
|
|
||||||
panning_2_rotation_linear,
|
|
||||||
panning_2_rotation_trigonometric,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .constants import (
|
from Musicreater.builtin_plugins.midi_read import (
|
||||||
# 字典键
|
|
||||||
MIDI_PROGRAM,
|
|
||||||
MIDI_PAN,
|
|
||||||
MIDI_VOLUME,
|
|
||||||
# 默认值
|
|
||||||
MIDI_DEFAULT_PROGRAM_VALUE,
|
|
||||||
MIDI_DEFAULT_VOLUME_VALUE,
|
MIDI_DEFAULT_VOLUME_VALUE,
|
||||||
|
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||||
|
volume_2_distance_straight as velocity_2_distance_straight,
|
||||||
|
volume_2_distance_natural as velocity_2_distance_natural,
|
||||||
|
panning_2_rotation_linear,
|
||||||
|
panning_2_rotation_trigonometric,
|
||||||
|
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
MM_DISLINK_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
MM_NBS_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
MM_NBS_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater.constants import (
|
||||||
# MIDI 表
|
# MIDI 表
|
||||||
MIDI_PITCH_NAME_TABLE,
|
MIDI_PITCH_NAME_TABLE,
|
||||||
MIDI_PITCHED_NOTE_NAME_GROUP,
|
MIDI_PITCHED_NOTE_NAME_GROUP,
|
||||||
@@ -133,12 +145,4 @@ from .constants import (
|
|||||||
# MIDI 到 我的世界 表
|
# MIDI 到 我的世界 表
|
||||||
MM_INSTRUMENT_RANGE_TABLE,
|
MM_INSTRUMENT_RANGE_TABLE,
|
||||||
MM_INSTRUMENT_DEVIATION_TABLE,
|
MM_INSTRUMENT_DEVIATION_TABLE,
|
||||||
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
|
|
||||||
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
|
|
||||||
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
|
||||||
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
|
||||||
MM_DISLINK_PITCHED_INSTRUMENT_TABLE,
|
|
||||||
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE,
|
|
||||||
MM_NBS_PITCHED_INSTRUMENT_TABLE,
|
|
||||||
MM_NBS_PERCUSSION_INSTRUMENT_TABLE,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,11 +36,41 @@ from itertools import chain
|
|||||||
|
|
||||||
import mido
|
import mido
|
||||||
|
|
||||||
from .constants import *
|
from Musicreater.constants import *
|
||||||
|
from Musicreater.exceptions import (
|
||||||
|
IllegalMinimumVolumeError,
|
||||||
|
NoteBinaryFileVerificationFailed as MusicSequenceVerificationFailed,
|
||||||
|
SingleNoteDecodeError,
|
||||||
|
NoteBinaryFileTypeError as MusicSequenceTypeError,
|
||||||
|
ZeroSpeedError,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.midi_read.constants import (
|
||||||
|
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||||
|
MIDI_DEFAULT_VOLUME_VALUE,
|
||||||
|
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
)
|
||||||
|
from Musicreater.builtin_plugins.midi_read.exceptions import (
|
||||||
|
NoteOnOffMismatchError,
|
||||||
|
LyricMismatchError,
|
||||||
|
)
|
||||||
|
from Musicreater.builtin_plugins.midi_read.utils import (
|
||||||
|
volume_2_distance_natural,
|
||||||
|
panning_2_rotation_trigonometric,
|
||||||
|
panning_2_rotation_linear,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.to_commands.progressbar import (
|
||||||
|
DEFAULT_PROGRESSBAR_STYLE,
|
||||||
|
ProgressBarStyle,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from .old_exceptions import *
|
from .old_exceptions import *
|
||||||
from .subclass import *
|
from .subclass import *
|
||||||
from .old_types import *
|
from .old_types import *
|
||||||
from .utils import *
|
from .old_utils import *
|
||||||
|
|
||||||
"""
|
"""
|
||||||
学习笔记:
|
学习笔记:
|
||||||
@@ -75,6 +105,10 @@ tick * tempo / 1000000.0 / ticks_per_beat * 一秒多少游戏刻
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
MIDI_PAN = "midi-pan"
|
||||||
|
MIDI_PROGRAM = "midi-program"
|
||||||
|
MIDI_VOLUME = "midi-volume"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class MusicSequence:
|
class MusicSequence:
|
||||||
@@ -167,7 +201,7 @@ class MusicSequence:
|
|||||||
pitched_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
pitched_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
percussion_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
percussion_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
minimum_vol: float = 0.1,
|
minimum_vol: float = 0.1,
|
||||||
volume_processing_function: FittingFunctionType = velocity_2_distance_natural,
|
volume_processing_function: FittingFunctionType = volume_2_distance_natural,
|
||||||
panning_processing_function: FittingFunctionType = panning_2_rotation_linear,
|
panning_processing_function: FittingFunctionType = panning_2_rotation_linear,
|
||||||
deviation: float = 0,
|
deviation: float = 0,
|
||||||
note_referance_table_replacement: Dict[str, str] = {},
|
note_referance_table_replacement: Dict[str, str] = {},
|
||||||
@@ -258,7 +292,7 @@ class MusicSequence:
|
|||||||
|
|
||||||
if bytes_buffer_in[:4] in (b"MSQ!", b"MSQ$"):
|
if bytes_buffer_in[:4] in (b"MSQ!", b"MSQ$"):
|
||||||
|
|
||||||
note_format_v3 = bytes_buffer_in[0] == b"MSQ$"
|
note_format_v3 = bytes_buffer_in[:4] == b"MSQ$"
|
||||||
|
|
||||||
group_1 = int.from_bytes(bytes_buffer_in[4:6], "big", signed=False)
|
group_1 = int.from_bytes(bytes_buffer_in[4:6], "big", signed=False)
|
||||||
group_2 = int.from_bytes(bytes_buffer_in[6:8], "big", signed=False)
|
group_2 = int.from_bytes(bytes_buffer_in[6:8], "big", signed=False)
|
||||||
@@ -270,7 +304,7 @@ class MusicSequence:
|
|||||||
8 : (stt_index := 8 + (group_1 >> 10))
|
8 : (stt_index := 8 + (group_1 >> 10))
|
||||||
].decode("GB18030")
|
].decode("GB18030")
|
||||||
|
|
||||||
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
channels_: MineNoteChannelType = enumerated_stuffcopy_dictionary(staff=[])
|
||||||
total_note_count = 0
|
total_note_count = 0
|
||||||
if verify:
|
if verify:
|
||||||
_header_index = stt_index
|
_header_index = stt_index
|
||||||
@@ -307,9 +341,9 @@ class MusicSequence:
|
|||||||
stt_index = end_index
|
stt_index = end_index
|
||||||
except Exception as _err:
|
except Exception as _err:
|
||||||
# print(channels_)
|
# print(channels_)
|
||||||
raise MusicSequenceDecodeError(
|
raise SingleNoteDecodeError(
|
||||||
_err, "当前全部通道数据:", channels_
|
"当前全部通道数据:", channels_
|
||||||
)
|
) from _err
|
||||||
if verify:
|
if verify:
|
||||||
if (
|
if (
|
||||||
_count_verify := xxh3_64(
|
_count_verify := xxh3_64(
|
||||||
@@ -412,7 +446,17 @@ class MusicSequence:
|
|||||||
_t6_buffer = _t2_buffer = 0
|
_t6_buffer = _t2_buffer = 0
|
||||||
|
|
||||||
_channel_inst_chart: Dict[str, Dict[str, int]] = {}
|
_channel_inst_chart: Dict[str, Dict[str, int]] = {}
|
||||||
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
"""
|
||||||
|
乐器对应通道的表
|
||||||
|
{
|
||||||
|
乐器名: {
|
||||||
|
"CNT": 当前通道的音符数量,
|
||||||
|
"INDEX": 当前乐器在通道中的索引
|
||||||
|
}
|
||||||
|
}
|
||||||
|
也就是说,一个通道可以对应多个乐器(多个乐器可能分配到同一个通道)
|
||||||
|
"""
|
||||||
|
channels_: MineNoteChannelType = enumerated_stuffcopy_dictionary(staff=[])
|
||||||
|
|
||||||
for i in range(total_note_count):
|
for i in range(total_note_count):
|
||||||
if verify:
|
if verify:
|
||||||
@@ -465,21 +509,31 @@ class MusicSequence:
|
|||||||
stt_index = end_index
|
stt_index = end_index
|
||||||
except Exception as _err:
|
except Exception as _err:
|
||||||
# print(bytes_buffer_in[stt_index:end_index])
|
# print(bytes_buffer_in[stt_index:end_index])
|
||||||
raise MusicSequenceDecodeError(
|
raise SingleNoteDecodeError(
|
||||||
_err, "所截取的音符码:", bytes_buffer_in[stt_index:end_index]
|
"所截取的音符码:", bytes_buffer_in[stt_index:end_index]
|
||||||
)
|
) from _err
|
||||||
|
|
||||||
|
# 按照乐器分配通道
|
||||||
if _read_note.sound_name in _channel_inst_chart:
|
if _read_note.sound_name in _channel_inst_chart:
|
||||||
|
# 如果本身已经有这个乐器了,那就直接加一个计数就可以了
|
||||||
_channel_inst_chart[_read_note.sound_name]["CNT"] += 1
|
_channel_inst_chart[_read_note.sound_name]["CNT"] += 1
|
||||||
else:
|
else:
|
||||||
|
# 如果没有这个乐器
|
||||||
if len(_channel_inst_chart) >= 16:
|
if len(_channel_inst_chart) >= 16:
|
||||||
|
# 已经超过了 16 个乐器,当前通道数量是装不下的
|
||||||
|
# 那就找一个音符数量最少的通道,把这个通道引用为
|
||||||
|
# 当前这个乐器的通道
|
||||||
_channel_inst_chart[_read_note.sound_name] = min(
|
_channel_inst_chart[_read_note.sound_name] = min(
|
||||||
_channel_inst_chart.values(), key=lambda x: x["CNT"]
|
_channel_inst_chart.values(), key=lambda x: x["CNT"]
|
||||||
) # 此处是指针式内存引用
|
) # 此处是指针式内存引用
|
||||||
|
_channel_inst_chart[_read_note.sound_name]["CNT"] += 1
|
||||||
|
else:
|
||||||
|
# 没有超过 16 个乐器,那就加!
|
||||||
_channel_inst_chart[_read_note.sound_name] = {
|
_channel_inst_chart[_read_note.sound_name] = {
|
||||||
"CNT": 1,
|
"CNT": 1,
|
||||||
"INDEX": len(_channel_inst_chart),
|
"INDEX": len(_channel_inst_chart),
|
||||||
}
|
}
|
||||||
|
# 把音符添加到对应的通道里边
|
||||||
channels_[_channel_inst_chart[_read_note.sound_name]["INDEX"]].append(
|
channels_[_channel_inst_chart[_read_note.sound_name]["INDEX"]].append(
|
||||||
_read_note
|
_read_note
|
||||||
)
|
)
|
||||||
@@ -522,7 +576,7 @@ class MusicSequence:
|
|||||||
music_name_ = bytes_buffer_in[
|
music_name_ = bytes_buffer_in[
|
||||||
8 : (stt_index := 8 + (group_1 >> 10))
|
8 : (stt_index := 8 + (group_1 >> 10))
|
||||||
].decode("GB18030")
|
].decode("GB18030")
|
||||||
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
channels_: MineNoteChannelType = enumerated_stuffcopy_dictionary(staff=[])
|
||||||
for channel_index in channels_.keys():
|
for channel_index in channels_.keys():
|
||||||
for i in range(
|
for i in range(
|
||||||
int.from_bytes(
|
int.from_bytes(
|
||||||
@@ -565,7 +619,7 @@ class MusicSequence:
|
|||||||
music_name_ = bytes_buffer_in[
|
music_name_ = bytes_buffer_in[
|
||||||
8 : (stt_index := 8 + (group_1 >> 10))
|
8 : (stt_index := 8 + (group_1 >> 10))
|
||||||
].decode("utf-8")
|
].decode("utf-8")
|
||||||
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
channels_: MineNoteChannelType = enumerated_stuffcopy_dictionary(staff=[])
|
||||||
for channel_index in channels_.keys():
|
for channel_index in channels_.keys():
|
||||||
for i in range(
|
for i in range(
|
||||||
int.from_bytes(
|
int.from_bytes(
|
||||||
@@ -796,7 +850,7 @@ class MusicSequence:
|
|||||||
def add_note(self, channel_no: int, note: MineNote, is_sort: bool = True):
|
def add_note(self, channel_no: int, note: MineNote, is_sort: bool = True):
|
||||||
"""
|
"""
|
||||||
在指定通道添加一个音符
|
在指定通道添加一个音符
|
||||||
值得注意:在版本 2.2.3 及之前 is_sort 参数默认为 False ;在此之后为 True
|
值得注意:在版本 2.2.3 及之前 is_sort 参数默认为 False;在此之后为 True
|
||||||
"""
|
"""
|
||||||
self.channels[channel_no].append(note)
|
self.channels[channel_no].append(note)
|
||||||
self.total_note_count += 1
|
self.total_note_count += 1
|
||||||
@@ -817,7 +871,7 @@ class MusicSequence:
|
|||||||
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||||
pitched_note_rtable: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
pitched_note_rtable: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
vol_processing_function: FittingFunctionType = velocity_2_distance_natural,
|
vol_processing_function: FittingFunctionType = volume_2_distance_natural,
|
||||||
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
|
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
|
||||||
note_rtable_replacement: Dict[str, str] = {},
|
note_rtable_replacement: Dict[str, str] = {},
|
||||||
) -> Tuple[MineNoteChannelType, int, Dict[str, int]]:
|
) -> Tuple[MineNoteChannelType, int, Dict[str, int]]:
|
||||||
@@ -857,10 +911,10 @@ class MusicSequence:
|
|||||||
raise ZeroSpeedError("播放速度不得为零,应为 (0,1] 范围内的实数。")
|
raise ZeroSpeedError("播放速度不得为零,应为 (0,1] 范围内的实数。")
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
midi_channels: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
midi_channels: MineNoteChannelType = enumerated_stuffcopy_dictionary(staff=[])
|
||||||
|
|
||||||
channel_controler: Dict[int, Dict[str, int]] = empty_midi_channels(
|
channel_controler: Dict[int, Dict[str, int]] = enumerated_stuffcopy_dictionary(
|
||||||
default_staff={
|
staff={
|
||||||
MIDI_PROGRAM: default_program_value,
|
MIDI_PROGRAM: default_program_value,
|
||||||
MIDI_VOLUME: default_volume_value,
|
MIDI_VOLUME: default_volume_value,
|
||||||
MIDI_PAN: 64,
|
MIDI_PAN: 64,
|
||||||
@@ -880,7 +934,7 @@ class MusicSequence:
|
|||||||
int,
|
int,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
] = empty_midi_channels(default_staff=[])
|
] = enumerated_stuffcopy_dictionary(staff=[])
|
||||||
note_queue_B: Dict[
|
note_queue_B: Dict[
|
||||||
int,
|
int,
|
||||||
List[
|
List[
|
||||||
@@ -889,7 +943,7 @@ class MusicSequence:
|
|||||||
int,
|
int,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
] = empty_midi_channels(default_staff=[])
|
] = enumerated_stuffcopy_dictionary(staff=[])
|
||||||
|
|
||||||
lyric_cache: List[Tuple[int, str]] = []
|
lyric_cache: List[Tuple[int, str]] = []
|
||||||
|
|
||||||
@@ -970,7 +1024,7 @@ class MusicSequence:
|
|||||||
|
|
||||||
# 更新结果信息
|
# 更新结果信息
|
||||||
midi_channels[msg.channel].append(
|
midi_channels[msg.channel].append(
|
||||||
that_note := midi_msgs_to_minenote(
|
that_note := midi_msgs_to_minenote( # 无法强行兼容了,pass
|
||||||
inst_=(
|
inst_=(
|
||||||
msg.note
|
msg.note
|
||||||
if (_is_percussion := (msg.channel == 9))
|
if (_is_percussion := (msg.channel == 9))
|
||||||
@@ -1096,7 +1150,7 @@ class MidiConvert(MusicSequence):
|
|||||||
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
enable_old_exe_format: bool = False,
|
enable_old_exe_format: bool = False,
|
||||||
minimum_volume: float = 0.1,
|
minimum_volume: float = 0.1,
|
||||||
vol_processing_function: FittingFunctionType = velocity_2_distance_natural,
|
vol_processing_function: FittingFunctionType = volume_2_distance_natural,
|
||||||
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
|
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
|
||||||
pitch_deviation: float = 0,
|
pitch_deviation: float = 0,
|
||||||
note_rtable_replacement: Dict[str, str] = {},
|
note_rtable_replacement: Dict[str, str] = {},
|
||||||
@@ -1179,7 +1233,7 @@ class MidiConvert(MusicSequence):
|
|||||||
percussion_note_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
percussion_note_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
old_exe_format: bool = False,
|
old_exe_format: bool = False,
|
||||||
min_volume: float = 0.1,
|
min_volume: float = 0.1,
|
||||||
vol_processing_func: FittingFunctionType = velocity_2_distance_natural,
|
vol_processing_func: FittingFunctionType = volume_2_distance_natural,
|
||||||
pan_processing_func: FittingFunctionType = panning_2_rotation_linear,
|
pan_processing_func: FittingFunctionType = panning_2_rotation_linear,
|
||||||
music_pitch_deviation: float = 0,
|
music_pitch_deviation: float = 0,
|
||||||
note_table_replacement: Dict[str, str] = {},
|
note_table_replacement: Dict[str, str] = {},
|
||||||
@@ -1274,7 +1328,7 @@ class MidiConvert(MusicSequence):
|
|||||||
-------
|
-------
|
||||||
list[MineCommand,]
|
list[MineCommand,]
|
||||||
"""
|
"""
|
||||||
pgs_style = progressbar_style.base_style
|
pgs_style = progressbar_style.style_base_string
|
||||||
"""用于被替换的进度条原始样式"""
|
"""用于被替换的进度条原始样式"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -1459,8 +1513,8 @@ class MidiConvert(MusicSequence):
|
|||||||
|
|
||||||
for i in range(pgs_style.count("_")):
|
for i in range(pgs_style.count("_")):
|
||||||
npg_stl = (
|
npg_stl = (
|
||||||
pgs_style.replace("_", progressbar_style.played_style, i + 1)
|
pgs_style.replace("_", progressbar_style.progress_played, i + 1)
|
||||||
.replace("_", progressbar_style.to_play_style)
|
.replace("_", progressbar_style.progress_toplay)
|
||||||
.replace(r"%%N", self.music_name)
|
.replace(r"%%N", self.music_name)
|
||||||
.replace(
|
.replace(
|
||||||
r"%%s",
|
r"%%s",
|
||||||
@@ -1700,7 +1754,7 @@ class MidiConvert(MusicSequence):
|
|||||||
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tick_delay=tickdelay,
|
delay=tickdelay,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
delaytime_previous = note.start_tick
|
delaytime_previous = note.start_tick
|
||||||
@@ -1786,7 +1840,7 @@ class MidiConvert(MusicSequence):
|
|||||||
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tick_delay=tickdelay,
|
delay=tickdelay,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
delaytime_previous[note.sound_name] = note.start_tick
|
delaytime_previous[note.sound_name] = note.start_tick
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ def to_addon_pack_in_score(
|
|||||||
"w",
|
"w",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
) as f:
|
) as f:
|
||||||
f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]]))
|
f.write("\n".join([single_cmd.mcfunction_command_string for single_cmd in cmdlist[i]]))
|
||||||
index_file.writelines(
|
index_file.writelines(
|
||||||
(
|
(
|
||||||
"scoreboard players add @a[scores={"
|
"scoreboard players add @a[scores={"
|
||||||
@@ -132,7 +132,7 @@ def to_addon_pack_in_score(
|
|||||||
f.writelines(
|
f.writelines(
|
||||||
"\n".join(
|
"\n".join(
|
||||||
[
|
[
|
||||||
single_cmd.cmd
|
single_cmd.mcfunction_command_string
|
||||||
for single_cmd in midi_cvt.form_progress_bar(
|
for single_cmd in midi_cvt.form_progress_bar(
|
||||||
maxscore, scoreboard_name, progressbar_style
|
maxscore, scoreboard_name, progressbar_style
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from typing import Literal
|
|||||||
|
|
||||||
from ...old_main import MidiConvert
|
from ...old_main import MidiConvert
|
||||||
from ...subclass import MineCommand
|
from ...subclass import MineCommand
|
||||||
from ..mcstructure import (
|
from Musicreater.builtin_plugins.commands_to_structure.mcstructure import (
|
||||||
COMPABILITY_VERSION_117,
|
COMPABILITY_VERSION_117,
|
||||||
COMPABILITY_VERSION_119,
|
COMPABILITY_VERSION_119,
|
||||||
commands_to_redstone_delay_structure,
|
commands_to_redstone_delay_structure,
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ def to_websocket_server(
|
|||||||
"title {} actionbar {}".format(
|
"title {} actionbar {}".format(
|
||||||
whom_to_play,
|
whom_to_play,
|
||||||
progressbar_style.play_output(
|
progressbar_style.play_output(
|
||||||
played_delays=i,
|
played_ticks=i,
|
||||||
total_delays=musics[music_to_play][1],
|
total_ticks=musics[music_to_play][1],
|
||||||
music_name=music_to_play,
|
music_name=music_to_play,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -111,7 +111,7 @@ def to_websocket_server(
|
|||||||
>= (cmd := musics[music_to_play][0][now_played_cmd]).delay
|
>= (cmd := musics[music_to_play][0][now_played_cmd]).delay
|
||||||
):
|
):
|
||||||
await self.send_command(
|
await self.send_command(
|
||||||
cmd.command_text.replace(replacement, whom_to_play),
|
cmd.command.replace(replacement, whom_to_play),
|
||||||
callback=self.cmd_feedback,
|
callback=self.cmd_feedback,
|
||||||
)
|
)
|
||||||
now_played_cmd += 1
|
now_played_cmd += 1
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
import math
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
# from io import BytesIO
|
# from io import BytesIO
|
||||||
@@ -34,37 +33,21 @@ from typing import (
|
|||||||
|
|
||||||
from xxhash import xxh3_64, xxh3_128, xxh32
|
from xxhash import xxh3_64, xxh3_128, xxh32
|
||||||
|
|
||||||
from .constants import (
|
from Musicreater.constants import (
|
||||||
MC_INSTRUMENT_BLOCKS_TABLE,
|
MC_INSTRUMENT_BLOCKS_TABLE,
|
||||||
MC_PITCHED_INSTRUMENT_LIST,
|
MC_PITCHED_INSTRUMENT_LIST,
|
||||||
MM_INSTRUMENT_DEVIATION_TABLE,
|
MM_INSTRUMENT_DEVIATION_TABLE,
|
||||||
MM_INSTRUMENT_RANGE_TABLE,
|
MM_INSTRUMENT_RANGE_TABLE,
|
||||||
)
|
)
|
||||||
from .old_exceptions import MusicSequenceDecodeError
|
from Musicreater.exceptions import SingleNoteDecodeError
|
||||||
|
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.midi_read.utils import midi_inst_to_mc_sound
|
||||||
|
|
||||||
from .subclass import MineNote, mctick2timestr, SingleNoteBox
|
from .subclass import MineNote, mctick2timestr, SingleNoteBox
|
||||||
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
||||||
|
|
||||||
|
|
||||||
def empty_midi_channels(
|
|
||||||
channel_count: int = 17, default_staff: Any = {}
|
|
||||||
) -> Dict[int, Any]:
|
|
||||||
"""
|
|
||||||
空MIDI通道字典
|
|
||||||
"""
|
|
||||||
|
|
||||||
return dict(
|
|
||||||
(
|
|
||||||
i,
|
|
||||||
(
|
|
||||||
default_staff.copy()
|
|
||||||
if isinstance(default_staff, (dict, list))
|
|
||||||
else default_staff
|
|
||||||
),
|
|
||||||
) # 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
|
||||||
for i in range(channel_count)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def inst_to_sould_with_deviation(
|
def inst_to_sould_with_deviation(
|
||||||
instrumentID: int,
|
instrumentID: int,
|
||||||
reference_table: MidiInstrumentTableType,
|
reference_table: MidiInstrumentTableType,
|
||||||
@@ -100,243 +83,7 @@ def inst_to_sould_with_deviation(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def midi_inst_to_mc_sound(
|
|
||||||
instrumentID: int,
|
|
||||||
reference_table: MidiInstrumentTableType,
|
|
||||||
default_instrument: str = "note.flute",
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
返回midi的乐器ID对应的我的世界乐器名
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
instrumentID: int
|
|
||||||
midi的乐器ID
|
|
||||||
reference_table: Dict[int, Tuple[str, int]]
|
|
||||||
转换乐器参照表
|
|
||||||
default_instrument: str
|
|
||||||
查无此乐器时的替换乐器
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
str我的世界乐器名
|
|
||||||
"""
|
|
||||||
return reference_table.get(
|
|
||||||
instrumentID,
|
|
||||||
default_instrument,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def velocity_2_distance_natural(
|
|
||||||
vol: float,
|
|
||||||
) -> float:
|
|
||||||
"""
|
|
||||||
midi力度值拟合成的距离函数
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
vol: int
|
|
||||||
midi 音符力度值
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
float播放中心到玩家的距离
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
-8.081720684086314
|
|
||||||
* math.log(
|
|
||||||
vol + 14.579508825070013,
|
|
||||||
)
|
|
||||||
+ 37.65806375944386
|
|
||||||
if vol < 60.64
|
|
||||||
else 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649)
|
|
||||||
+ -6.313841334963396 * (vol + 2592.272889454798)
|
|
||||||
+ 4558.496367823575
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def velocity_2_distance_straight(vol: float) -> float:
|
|
||||||
"""
|
|
||||||
midi力度值拟合成的距离函数
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
vol: int
|
|
||||||
midi 音符力度值
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
float播放中心到玩家的距离
|
|
||||||
"""
|
|
||||||
return vol / -8 + 16
|
|
||||||
|
|
||||||
|
|
||||||
def panning_2_rotation_linear(pan_: float) -> float:
|
|
||||||
"""
|
|
||||||
Midi 左右平衡偏移值线性转为声源旋转角度
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
pan_: int
|
|
||||||
Midi 左右平衡偏移值
|
|
||||||
注:此参数为int,范围从 0 到 127,当为 64 时,声源居中
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
float
|
|
||||||
声源旋转角度
|
|
||||||
"""
|
|
||||||
return (pan_ - 64) * 90 / 63
|
|
||||||
|
|
||||||
|
|
||||||
def panning_2_rotation_trigonometric(pan_: float) -> float:
|
|
||||||
"""
|
|
||||||
Midi 左右平衡偏移值,依照圆的声场定位,转为声源旋转角度
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
pan_: int
|
|
||||||
Midi 左右平衡偏移值
|
|
||||||
注:此参数为int,范围从 0 到 127,当为 64 时,声源居中
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
float
|
|
||||||
声源旋转角度
|
|
||||||
"""
|
|
||||||
if pan_ <= 0:
|
|
||||||
return -90
|
|
||||||
elif pan_ >= 127:
|
|
||||||
return 90
|
|
||||||
else:
|
|
||||||
return math.degrees(math.acos((64 - pan_) / 63)) - 90
|
|
||||||
|
|
||||||
|
|
||||||
def minenote_to_command_parameters(
|
|
||||||
mine_note: MineNote,
|
|
||||||
pitch_deviation: float = 0,
|
|
||||||
) -> Tuple[
|
|
||||||
str,
|
|
||||||
Tuple[float, float, float],
|
|
||||||
float,
|
|
||||||
Union[float, Literal[None]],
|
|
||||||
]:
|
|
||||||
"""
|
|
||||||
将 MineNote 对象转为《我的世界》音符播放所需之参数
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
------------
|
|
||||||
mine_note: MineNote
|
|
||||||
音符对象
|
|
||||||
deviation: float
|
|
||||||
音调偏移量
|
|
||||||
|
|
||||||
Returns
|
|
||||||
---------
|
|
||||||
str, tuple[float, float, float], float, float
|
|
||||||
我的世界音符ID, 播放视角坐标, 指令音量参数, 指令音调参数
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
mine_note.sound_name,
|
|
||||||
mine_note.position_displacement,
|
|
||||||
mine_note.velocity / 127,
|
|
||||||
(
|
|
||||||
None
|
|
||||||
if mine_note.percussive
|
|
||||||
else (
|
|
||||||
2
|
|
||||||
** (
|
|
||||||
(
|
|
||||||
mine_note.note_pitch
|
|
||||||
- 60
|
|
||||||
- MM_INSTRUMENT_DEVIATION_TABLE.get(mine_note.sound_name, 6)
|
|
||||||
+ pitch_deviation
|
|
||||||
)
|
|
||||||
/ 12
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def midi_msgs_to_minenote(
|
|
||||||
inst_: int, # 乐器编号
|
|
||||||
note_: int,
|
|
||||||
percussive_: bool, # 是否作为打击乐器启用
|
|
||||||
volume_: int,
|
|
||||||
velocity_: int,
|
|
||||||
panning_: int,
|
|
||||||
start_time_: int,
|
|
||||||
duration_: int,
|
|
||||||
play_speed: float,
|
|
||||||
midi_reference_table: MidiInstrumentTableType,
|
|
||||||
volume_processing_method_: FittingFunctionType,
|
|
||||||
panning_processing_method_: FittingFunctionType,
|
|
||||||
note_table_replacement: Dict[str, str] = {},
|
|
||||||
lyric_line: str = "",
|
|
||||||
) -> MineNote:
|
|
||||||
"""
|
|
||||||
将Midi信息转为我的世界音符对象
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
------------
|
|
||||||
inst_: int
|
|
||||||
乐器编号
|
|
||||||
note_: int
|
|
||||||
音高编号(音符编号)
|
|
||||||
percussive_: bool
|
|
||||||
是否作为打击乐器启用
|
|
||||||
volume_: int
|
|
||||||
音量
|
|
||||||
velocity_: int
|
|
||||||
力度
|
|
||||||
panning_: int
|
|
||||||
声相偏移
|
|
||||||
start_time_: int
|
|
||||||
音符起始时间(微秒)
|
|
||||||
duration_: int
|
|
||||||
音符持续时间(微秒)
|
|
||||||
play_speed: float
|
|
||||||
曲目播放速度
|
|
||||||
midi_reference_table: Dict[int, str]
|
|
||||||
转换对照表
|
|
||||||
volume_processing_method_: Callable[[float], float]
|
|
||||||
音量处理函数
|
|
||||||
panning_processing_method_: Callable[[float], float]
|
|
||||||
立体声相偏移处理函数
|
|
||||||
note_table_replacement: Dict[str, str]
|
|
||||||
音符替换表,定义 Minecraft 音符字串的替换
|
|
||||||
lyric_line: str
|
|
||||||
该音符的歌词
|
|
||||||
|
|
||||||
Returns
|
|
||||||
---------
|
|
||||||
MineNote
|
|
||||||
我的世界音符对象
|
|
||||||
"""
|
|
||||||
mc_sound_ID = midi_inst_to_mc_sound(
|
|
||||||
inst_,
|
|
||||||
midi_reference_table,
|
|
||||||
"note.bd" if percussive_ else "note.flute",
|
|
||||||
)
|
|
||||||
|
|
||||||
return MineNote(
|
|
||||||
mc_sound_name=note_table_replacement.get(mc_sound_ID, mc_sound_ID),
|
|
||||||
midi_pitch=note_,
|
|
||||||
midi_velocity=velocity_,
|
|
||||||
start_time=(tk := int(start_time_ / float(play_speed) / 50000)),
|
|
||||||
last_time=round(duration_ / float(play_speed) / 50000),
|
|
||||||
mass_precision_time=round((start_time_ / float(play_speed) - tk * 50000) / 800),
|
|
||||||
is_percussion=percussive_,
|
|
||||||
distance=volume_processing_method_(volume_),
|
|
||||||
azimuth=(panning_processing_method_(panning_), 0),
|
|
||||||
extra_information={
|
|
||||||
"LYRIC_TEXT": lyric_line,
|
|
||||||
"VOLUME_VALUE": volume_,
|
|
||||||
"PIN_VALUE": panning_,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def midi_msgs_to_minenote_using_kami_respack(
|
def midi_msgs_to_minenote_using_kami_respack(
|
||||||
@@ -634,11 +381,10 @@ def load_decode_fsq_flush_release(
|
|||||||
)
|
)
|
||||||
except Exception as _err:
|
except Exception as _err:
|
||||||
# print(bytes_buffer_in[stt_index:end_index])
|
# print(bytes_buffer_in[stt_index:end_index])
|
||||||
raise MusicSequenceDecodeError(
|
raise SingleNoteDecodeError(
|
||||||
_err,
|
|
||||||
"所截取的音符码之首个字节:",
|
"所截取的音符码之首个字节:",
|
||||||
_first_byte,
|
_first_byte,
|
||||||
)
|
) from _err
|
||||||
|
|
||||||
|
|
||||||
def load_decode_msq_flush_release(
|
def load_decode_msq_flush_release(
|
||||||
@@ -685,8 +431,8 @@ def load_decode_msq_flush_release(
|
|||||||
|
|
||||||
_total_note_count = 1
|
_total_note_count = 1
|
||||||
|
|
||||||
_channel_infos = empty_midi_channels(
|
_channel_infos = enumerated_stuffcopy_dictionary(
|
||||||
default_staff={"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
|
staff={"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
|
||||||
)
|
)
|
||||||
|
|
||||||
for __channel_index in _channel_infos.keys():
|
for __channel_index in _channel_infos.keys():
|
||||||
@@ -815,7 +561,7 @@ def load_decode_msq_flush_release(
|
|||||||
_total_note_count -= 1
|
_total_note_count -= 1
|
||||||
except Exception as _err:
|
except Exception as _err:
|
||||||
# print(channels_)
|
# print(channels_)
|
||||||
raise MusicSequenceDecodeError("难以定位的解码错误", _err)
|
raise SingleNoteDecodeError("难以定位的解码错误") from _err
|
||||||
if not _read_in_note_list:
|
if not _read_in_note_list:
|
||||||
break
|
break
|
||||||
# _note_list.append
|
# _note_list.append
|
||||||
@@ -20,7 +20,13 @@ from math import sin, cos, asin, radians, degrees, sqrt, atan
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Any, List, Tuple, Union, Dict, Sequence
|
from typing import Optional, Any, List, Tuple, Union, Dict, Sequence
|
||||||
|
|
||||||
from .constants import MC_PITCHED_INSTRUMENT_LIST
|
from Musicreater.constants import MC_PITCHED_INSTRUMENT_LIST
|
||||||
|
from Musicreater.builtin_plugins.to_commands.main import MineCommand
|
||||||
|
from Musicreater.builtin_plugins.to_commands.progressbar import (
|
||||||
|
ProgressBarStyle,
|
||||||
|
mctick2timestr,
|
||||||
|
DEFAULT_PROGRESSBAR_STYLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
@@ -525,80 +531,6 @@ class MineNote:
|
|||||||
return self.tuplize() == other.tuplize()
|
return self.tuplize() == other.tuplize()
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
|
||||||
class MineCommand:
|
|
||||||
"""存储单个指令的类"""
|
|
||||||
|
|
||||||
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 MineCommand(
|
|
||||||
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)
|
@dataclass(init=False)
|
||||||
class SingleNoteBox:
|
class SingleNoteBox:
|
||||||
"""存储单个音符盒"""
|
"""存储单个音符盒"""
|
||||||
@@ -704,158 +636,3 @@ class SingleNoteBox:
|
|||||||
if not isinstance(other, self.__class__):
|
if not isinstance(other, self.__class__):
|
||||||
return False
|
return False
|
||||||
return self.__str__() == other.__str__()
|
return self.__str__() == other.__str__()
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
|
||||||
class ProgressBarStyle:
|
|
||||||
"""进度条样式类"""
|
|
||||||
|
|
||||||
base_style: str
|
|
||||||
"""基础样式"""
|
|
||||||
|
|
||||||
to_play_style: str
|
|
||||||
"""未播放之样式"""
|
|
||||||
|
|
||||||
played_style: str
|
|
||||||
"""已播放之样式"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
base_s: Optional[str] = None,
|
|
||||||
to_play_s: Optional[str] = None,
|
|
||||||
played_s: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
用于存储进度条样式的类
|
|
||||||
|
|
||||||
| 标识符 | 指定的可变量 |
|
|
||||||
|---------|----------------|
|
|
||||||
| `%%N` | 乐曲名(即传入的文件名)|
|
|
||||||
| `%%s` | 当前计分板值 |
|
|
||||||
| `%^s` | 计分板最大值 |
|
|
||||||
| `%%t` | 当前播放时间 |
|
|
||||||
| `%^t` | 曲目总时长 |
|
|
||||||
| `%%%` | 当前进度比率 |
|
|
||||||
| `_` | 用以表示进度条占位|
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
------------
|
|
||||||
base_s: str
|
|
||||||
基础样式,用以定义进度条整体
|
|
||||||
to_play_s: str
|
|
||||||
进度条样式:尚未播放的样子
|
|
||||||
played_s: str
|
|
||||||
已经播放的样子
|
|
||||||
|
|
||||||
Returns
|
|
||||||
---------
|
|
||||||
ProgressBarStyle 类
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.base_style = (
|
|
||||||
base_s if base_s else r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]"
|
|
||||||
)
|
|
||||||
self.to_play_style = to_play_s if to_play_s else r"§7="
|
|
||||||
self.played_style = played_s if played_s else r"="
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_tuple(cls, tuplized_style: Optional[Tuple[str, Tuple[str, str]]]):
|
|
||||||
"""自旧版进度条元组表示法读入数据(已不建议使用)"""
|
|
||||||
|
|
||||||
if tuplized_style is None:
|
|
||||||
return cls(
|
|
||||||
r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]",
|
|
||||||
r"§7=",
|
|
||||||
r"=",
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(tuplized_style, tuple):
|
|
||||||
if isinstance(tuplized_style[0], str) and isinstance(
|
|
||||||
tuplized_style[1], tuple
|
|
||||||
):
|
|
||||||
if isinstance(tuplized_style[1][0], str) and isinstance(
|
|
||||||
tuplized_style[1][1], str
|
|
||||||
):
|
|
||||||
return cls(
|
|
||||||
tuplized_style[0], tuplized_style[1][0], tuplized_style[1][1]
|
|
||||||
)
|
|
||||||
raise ValueError(
|
|
||||||
"元组表示的进度条样式组 {} 格式错误,已不建议使用此功能,请尽快更换。".format(
|
|
||||||
tuplized_style
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_base_style(self, value: str):
|
|
||||||
"""设置基础样式"""
|
|
||||||
self.base_style = value
|
|
||||||
|
|
||||||
def set_to_play_style(self, value: str):
|
|
||||||
"""设置未播放之样式"""
|
|
||||||
self.to_play_style = value
|
|
||||||
|
|
||||||
def set_played_style(self, value: str):
|
|
||||||
"""设置已播放之样式"""
|
|
||||||
self.played_style = value
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
dst = ProgressBarStyle(self.base_style, self.to_play_style, self.played_style)
|
|
||||||
return dst
|
|
||||||
|
|
||||||
def play_output(
|
|
||||||
self,
|
|
||||||
played_delays: int,
|
|
||||||
total_delays: int,
|
|
||||||
music_name: str = "无题",
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
直接依照此格式输出一个进度条
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
------------
|
|
||||||
played_delays: int
|
|
||||||
当前播放进度积分值
|
|
||||||
total_delays: int
|
|
||||||
乐器总延迟数(计分板值)
|
|
||||||
music_name: str
|
|
||||||
曲名
|
|
||||||
|
|
||||||
Returns
|
|
||||||
---------
|
|
||||||
str
|
|
||||||
进度条字符串
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
self.base_style.replace(r"%%N", music_name)
|
|
||||||
.replace(r"%%s", str(played_delays))
|
|
||||||
.replace(r"%^s", str(total_delays))
|
|
||||||
.replace(r"%%t", mctick2timestr(played_delays))
|
|
||||||
.replace(r"%^t", mctick2timestr(total_delays))
|
|
||||||
.replace(
|
|
||||||
r"%%%",
|
|
||||||
"{:0>5.2f}%".format(int(10000 * played_delays / total_delays) / 100),
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
"_",
|
|
||||||
self.played_style,
|
|
||||||
(played_delays * self.base_style.count("_") // total_delays) + 1,
|
|
||||||
)
|
|
||||||
.replace("_", self.to_play_style)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def mctick2timestr(mc_tick: int) -> str:
|
|
||||||
"""
|
|
||||||
将《我的世界》的游戏刻计转为表示时间的字符串
|
|
||||||
"""
|
|
||||||
return "{:0>2d}:{:0>2d}".format(mc_tick // 1200, (mc_tick // 20) % 60)
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
|
|
||||||
r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]",
|
|
||||||
r"§7=",
|
|
||||||
r"=",
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
默认的进度条样式
|
|
||||||
"""
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def to_zip_pack_in_score(
|
|||||||
"w",
|
"w",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
) as f:
|
) as f:
|
||||||
f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]]))
|
f.write("\n".join([single_cmd.mcfunction_command_string for single_cmd in cmdlist[i]]))
|
||||||
index_file.writelines(
|
index_file.writelines(
|
||||||
(
|
(
|
||||||
"scoreboard players add @a[score_{0}_min=1] {0} 1\n".format(
|
"scoreboard players add @a[score_{0}_min=1] {0} 1\n".format(
|
||||||
@@ -97,7 +97,7 @@ def to_zip_pack_in_score(
|
|||||||
f.writelines(
|
f.writelines(
|
||||||
"\n".join(
|
"\n".join(
|
||||||
[
|
[
|
||||||
single_cmd.cmd
|
single_cmd.mcfunction_command_string
|
||||||
for single_cmd in midi_cvt.form_java_progress_bar(
|
for single_cmd in midi_cvt.form_java_progress_bar(
|
||||||
maxscore, scoreboard_name, progressbar_style
|
maxscore, scoreboard_name, progressbar_style
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
requires-python = ">= 3.8, < 4.0"
|
requires-python = ">= 3.8, < 4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mido >= 1.3",
|
"tomli >= 2.4.0, < 3.0 ; python_version < '3.11'",
|
||||||
"tomli >= 2.4.0; python_version < '3.11'",
|
"tomli-w >= 1.0.0, < 2.0",
|
||||||
"tomli-w >= 1.0.0",
|
"xxhash >= 3.0, < 4.0",
|
||||||
"xxhash >= 3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
authors = [
|
authors = [
|
||||||
@@ -46,14 +45,25 @@
|
|||||||
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
full = [
|
midi = [
|
||||||
"TrimMCStruct <= 0.0.5.9",
|
"mido >= 1.3, < 2.0",
|
||||||
"brotli >= 1.0.0",
|
]
|
||||||
|
structure = [
|
||||||
"numpy",
|
"numpy",
|
||||||
|
"TrimMCStruct <= 0.0.5.9",
|
||||||
|
"brotli >= 1.0.0, < 2.0",
|
||||||
|
]
|
||||||
|
full = [
|
||||||
|
"mido >= 1.3, < 2.0",
|
||||||
|
"numpy",
|
||||||
|
"TrimMCStruct <= 0.0.5.9",
|
||||||
|
"brotli >= 1.0.0, < 2.0",
|
||||||
]
|
]
|
||||||
dev = [
|
dev = [
|
||||||
|
"mido >= 1.3, < 2.0",
|
||||||
|
"numpy",
|
||||||
"TrimMCStruct <= 0.0.5.9",
|
"TrimMCStruct <= 0.0.5.9",
|
||||||
"brotli >= 1.0.0",
|
"brotli >= 1.0.0, < 2.0",
|
||||||
"dill",
|
"dill",
|
||||||
"rich",
|
"rich",
|
||||||
"pyinstaller",
|
"pyinstaller",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.6 MiB After Width: | Height: | Size: 130 KiB |
32
resources/test/pgb-animate.json
Normal file
32
resources/test/pgb-animate.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"rawtext": [
|
||||||
|
{
|
||||||
|
"translate": "%%4",
|
||||||
|
"with": {
|
||||||
|
"rawtext": [
|
||||||
|
{
|
||||||
|
"selector": "@e[name=某实体,scores={计分板=0..93}]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "@e[name=某实体,scores={计分板=1..93}]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "@e[name=某实体,scores={计分板=92..93}]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "显示第一段"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "显示第二段"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "显示第三段"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "NaN"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
34
test_convert_midi.py
Normal file
34
test_convert_midi.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# 一个简单的项目实践测试
|
||||||
|
from pathlib import Path
|
||||||
|
from Musicreater import load_plugin_module, MusiCreater
|
||||||
|
from Musicreater.plugins import _global_plugin_registry
|
||||||
|
|
||||||
|
load_plugin_module("Musicreater.builtin_plugins.midi_read")
|
||||||
|
load_plugin_module("Musicreater.builtin_plugins.to_commands")
|
||||||
|
load_plugin_module("Musicreater.builtin_plugins.commands_to_structure")
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.midi_read import MidiImportConfig
|
||||||
|
from Musicreater.builtin_plugins.commands_to_structure import McstructureExportConfig
|
||||||
|
|
||||||
|
print("当前支持的导入格式:", _global_plugin_registry.supported_input_formats())
|
||||||
|
print("当前支持的导出格式:", _global_plugin_registry.supported_output_formats())
|
||||||
|
|
||||||
|
msct = MusiCreater.import_music(
|
||||||
|
Path("./resources/测试片段.mid"), plugin_config=MidiImportConfig()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
print("全局插件注册表:", _global_plugin_registry)
|
||||||
|
print("插件缓存字典:", msct._plugin_cache)
|
||||||
|
|
||||||
|
|
||||||
|
print(msct.music.music_name)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"大小、音乐总长:",
|
||||||
|
msct.export_music(
|
||||||
|
Path("./output.mcstructure"),
|
||||||
|
plugin_id="music_to_mcstructure_in_delay_plugin",
|
||||||
|
plugin_config=McstructureExportConfig(),
|
||||||
|
),
|
||||||
|
)
|
||||||
29
test_read.py
29
test_read.py
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# 一个简单的项目实践测试
|
# 一个简单的项目实践测试
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from Musicreater import load_plugin_module, MusiCreater
|
from Musicreater import load_plugin_module, MusiCreater
|
||||||
@@ -6,19 +5,35 @@ from Musicreater.plugins import _global_plugin_registry
|
|||||||
|
|
||||||
load_plugin_module("Musicreater.builtin_plugins.midi_read")
|
load_plugin_module("Musicreater.builtin_plugins.midi_read")
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.midi_read import MidiImportConfig
|
||||||
|
|
||||||
print("当前支持的导入格式:", _global_plugin_registry.supported_input_formats())
|
print("当前支持的导入格式:", _global_plugin_registry.supported_input_formats())
|
||||||
print("当前支持的导出格式:", _global_plugin_registry.supported_output_formats())
|
print("当前支持的导出格式:", _global_plugin_registry.supported_output_formats())
|
||||||
|
|
||||||
print(msct:=MusiCreater.import_music(Path("./resources/测试片段.mid")))
|
print(msct := MusiCreater.import_music(Path("./resources/测试片段.mid")))
|
||||||
|
|
||||||
print(msct.music)
|
print(msct.music)
|
||||||
|
|
||||||
|
# 如果要直接访问插件里面的函数:
|
||||||
# 为了让类型检查器满意,以下方法不建议使用,因为这本质上是越过了 MusiCreater 类而直接执行插件的函数
|
# 为了确保类型安全,以下方法不建议使用,因为这本质上是越过了 MusiCreater 类而直接执行插件的函数
|
||||||
print(t := msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), None))
|
print(t := msct.midi_to_music_plugin.load(Path("./resources/测试片段.mid"), None)) # type: ignore
|
||||||
# 我们建议用这种方式来代替
|
# 我们建议用这种方式来代替
|
||||||
t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(Path("./resources/测试片段.mid"), None)
|
t = _global_plugin_registry._music_input_plugins["midi_to_music_plugin"].load(
|
||||||
|
Path("./resources/测试片段.mid"),
|
||||||
|
MidiImportConfig(
|
||||||
|
speed_multiplier=1.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# 或者
|
||||||
|
from Musicreater.plugins import MusicInputPluginBase
|
||||||
|
|
||||||
|
if isinstance((p := msct.midi_to_music_plugin), MusicInputPluginBase):
|
||||||
|
t = p.load(Path("./resources/测试片段.mid"), None)
|
||||||
|
|
||||||
|
# 但是说实话,既然已经在 MusiCreater 类中提供了
|
||||||
|
# import_music、export_music、perform_operation_on_music 等方法,
|
||||||
|
# 那么我们不建议使用上面展示的调取插件的方式来执行插件内的函数。
|
||||||
|
msct.perform_operation_on_music
|
||||||
|
|
||||||
print(_global_plugin_registry)
|
print(_global_plugin_registry)
|
||||||
print(msct._plugin_cache)
|
print(msct._plugin_cache)
|
||||||
|
|
||||||
|
|||||||
40
uv.lock
generated
40
uv.lock
generated
@@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 3
|
revision = 2
|
||||||
requires-python = ">=3.8, <4.0"
|
requires-python = ">=3.8, <4.0"
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.10'",
|
"python_full_version >= '3.10'",
|
||||||
@@ -577,8 +577,7 @@ wheels = [
|
|||||||
name = "musicreater"
|
name = "musicreater"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "mido" },
|
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||||
{ name = "tomli" },
|
|
||||||
{ name = "tomli-w", version = "1.0.0", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.9'" },
|
{ name = "tomli-w", version = "1.0.0", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.9'" },
|
||||||
{ name = "tomli-w", version = "1.2.0", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version >= '3.9'" },
|
{ name = "tomli-w", version = "1.2.0", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version >= '3.9'" },
|
||||||
{ name = "xxhash" },
|
{ name = "xxhash" },
|
||||||
@@ -588,12 +587,27 @@ dependencies = [
|
|||||||
dev = [
|
dev = [
|
||||||
{ name = "brotli" },
|
{ name = "brotli" },
|
||||||
{ name = "dill" },
|
{ name = "dill" },
|
||||||
|
{ name = "mido" },
|
||||||
|
{ name = "numpy", version = "1.24.4", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.9'" },
|
||||||
|
{ name = "numpy", version = "2.0.2", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version == '3.9.*'" },
|
||||||
|
{ name = "numpy", version = "2.2.6", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version >= '3.10'" },
|
||||||
{ name = "pyinstaller" },
|
{ name = "pyinstaller" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
{ name = "trimmcstruct" },
|
{ name = "trimmcstruct" },
|
||||||
{ name = "twine" },
|
{ name = "twine" },
|
||||||
]
|
]
|
||||||
full = [
|
full = [
|
||||||
|
{ name = "brotli" },
|
||||||
|
{ name = "mido" },
|
||||||
|
{ name = "numpy", version = "1.24.4", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.9'" },
|
||||||
|
{ name = "numpy", version = "2.0.2", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version == '3.9.*'" },
|
||||||
|
{ name = "numpy", version = "2.2.6", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version >= '3.10'" },
|
||||||
|
{ name = "trimmcstruct" },
|
||||||
|
]
|
||||||
|
midi = [
|
||||||
|
{ name = "mido" },
|
||||||
|
]
|
||||||
|
structure = [
|
||||||
{ name = "brotli" },
|
{ name = "brotli" },
|
||||||
{ name = "numpy", version = "1.24.4", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.9'" },
|
{ name = "numpy", version = "1.24.4", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.9'" },
|
||||||
{ name = "numpy", version = "2.0.2", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version == '3.9.*'" },
|
{ name = "numpy", version = "2.0.2", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version == '3.9.*'" },
|
||||||
@@ -603,21 +617,27 @@ full = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "brotli", marker = "extra == 'dev'", specifier = ">=1.0.0" },
|
{ name = "brotli", marker = "extra == 'dev'", specifier = ">=1.0.0,<2.0" },
|
||||||
{ name = "brotli", marker = "extra == 'full'", specifier = ">=1.0.0" },
|
{ name = "brotli", marker = "extra == 'full'", specifier = ">=1.0.0,<2.0" },
|
||||||
|
{ name = "brotli", marker = "extra == 'structure'", specifier = ">=1.0.0,<2.0" },
|
||||||
{ name = "dill", marker = "extra == 'dev'" },
|
{ name = "dill", marker = "extra == 'dev'" },
|
||||||
{ name = "mido", specifier = ">=1.3" },
|
{ name = "mido", marker = "extra == 'dev'", specifier = ">=1.3,<2.0" },
|
||||||
|
{ name = "mido", marker = "extra == 'full'", specifier = ">=1.3,<2.0" },
|
||||||
|
{ name = "mido", marker = "extra == 'midi'", specifier = ">=1.3,<2.0" },
|
||||||
|
{ name = "numpy", marker = "extra == 'dev'" },
|
||||||
{ name = "numpy", marker = "extra == 'full'" },
|
{ name = "numpy", marker = "extra == 'full'" },
|
||||||
|
{ name = "numpy", marker = "extra == 'structure'" },
|
||||||
{ name = "pyinstaller", marker = "extra == 'dev'" },
|
{ name = "pyinstaller", marker = "extra == 'dev'" },
|
||||||
{ name = "rich", marker = "extra == 'dev'" },
|
{ name = "rich", marker = "extra == 'dev'" },
|
||||||
{ name = "tomli" },
|
{ name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.4.0,<3.0" },
|
||||||
{ name = "tomli-w" },
|
{ name = "tomli-w", specifier = ">=1.0.0,<2.0" },
|
||||||
{ name = "trimmcstruct", marker = "extra == 'dev'", specifier = "<=0.0.5.9" },
|
{ name = "trimmcstruct", marker = "extra == 'dev'", specifier = "<=0.0.5.9" },
|
||||||
{ name = "trimmcstruct", marker = "extra == 'full'", specifier = "<=0.0.5.9" },
|
{ name = "trimmcstruct", marker = "extra == 'full'", specifier = "<=0.0.5.9" },
|
||||||
|
{ name = "trimmcstruct", marker = "extra == 'structure'", specifier = "<=0.0.5.9" },
|
||||||
{ name = "twine", marker = "extra == 'dev'" },
|
{ name = "twine", marker = "extra == 'dev'" },
|
||||||
{ name = "xxhash", specifier = ">=3" },
|
{ name = "xxhash", specifier = ">=3.0,<4.0" },
|
||||||
]
|
]
|
||||||
provides-extras = ["full", "dev"]
|
provides-extras = ["midi", "structure", "full", "dev"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mutf8"
|
name = "mutf8"
|
||||||
|
|||||||
Reference in New Issue
Block a user