mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-29 04:35:50 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
825d275a5e | ||
|
|
de830262a7 | ||
|
|
9c1383360b | ||
|
|
60cbdfb9d0 | ||
|
|
d6944392cd | ||
|
|
ba7b10a25f | ||
|
|
307feb9b24 | ||
|
|
6e518dada4 | ||
|
|
4c036cbd4c | ||
|
|
c310ba5dc3 | ||
|
|
d9c92fa269 | ||
|
|
00c445f7ad | ||
|
|
3ee686c712 | ||
|
|
0e95a1e541 | ||
|
|
bbc67921d6 | ||
|
|
62cd4a0c94 | ||
|
|
295da53c60 | ||
|
|
fff8e43f53 | ||
|
|
2a5ccb8eeb | ||
|
|
d4901cf3dc | ||
|
|
13512df9ce | ||
|
|
1d9931f79d | ||
|
|
841f6e53c6 | ||
|
|
0de959c396 | ||
|
|
734ee2dd66 | ||
|
|
32b7930b26 | ||
|
|
583ca04ac9 |
@@ -1,19 +1,24 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""一个简单的我的世界音频转换库
|
|
||||||
音·创 (Musicreater)
|
"""
|
||||||
|
音·创
|
||||||
是一款免费开源的《我的世界》数字音频支持库。
|
是一款免费开源的《我的世界》数字音频支持库。
|
||||||
|
|
||||||
Musicreater (音·创)
|
Musicreater (音·创)
|
||||||
A free open source library used for dealing with **Minecraft** digital musics.
|
A cost free and open-source library for handling with **Minecraft** digital music.
|
||||||
|
|
||||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
版权所有 © 2026 睿乐组织
|
||||||
Copyright © 2025 Eilles & bgArray
|
Copyright © 2026 TriM-Organization
|
||||||
|
|
||||||
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
|
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
|
||||||
The Licensor of Musicreater("this project") is Eilles, bgArray.
|
The Licensor of Musicreater("this project") is Eilles, YuhengAlioth
|
||||||
|
|
||||||
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||||
任何人皆可从以下地址获得本协议副本:https://gitee.com/EillesWan/YulvLicenses。
|
任何人皆可从以下地址获得本协议副本:
|
||||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
|
||||||
|
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,
|
||||||
|
不予提供任何形式的担保、任何明示、任何暗示或类似承诺。
|
||||||
|
也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||||
详细的准许和限制条款请见原协议文本。
|
详细的准许和限制条款请见原协议文本。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -22,123 +27,44 @@ The Licensor of Musicreater("this project") is Eilles, bgArray.
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__version__ = "2.4.2.3"
|
__version__ = "3.0.0-alpha"
|
||||||
__vername__ = "音符附加信息升级"
|
|
||||||
__author__ = (
|
__author__ = (
|
||||||
("金羿", "Eilles"),
|
("金羿", "Eilles"),
|
||||||
("诸葛亮与八卦阵", "bgArray"),
|
("玉衡Alioth", "YuhengAlioth"),
|
||||||
("鱼旧梦", "ElapsingDreams"),
|
("鱼旧梦", "ElapsingDreams"),
|
||||||
("偷吃不是Touch", "Touch"),
|
|
||||||
)
|
)
|
||||||
__all__ = [
|
|
||||||
# 主要类
|
|
||||||
"MusicSequence",
|
|
||||||
"MidiConvert",
|
|
||||||
# 附加类
|
|
||||||
# "SingleNote",
|
|
||||||
"MineNote",
|
|
||||||
"MineCommand",
|
|
||||||
"SingleNoteBox",
|
|
||||||
"ProgressBarStyle",
|
|
||||||
# "TimeStamp", 未来功能
|
|
||||||
# 字典键
|
|
||||||
"MIDI_PROGRAM",
|
|
||||||
"MIDI_VOLUME",
|
|
||||||
"MIDI_PAN",
|
|
||||||
# 默认值
|
|
||||||
"MIDI_DEFAULT_PROGRAM_VALUE",
|
|
||||||
"MIDI_DEFAULT_VOLUME_VALUE",
|
|
||||||
"DEFAULT_PROGRESSBAR_STYLE",
|
|
||||||
# Midi 自己的对照表
|
|
||||||
"MIDI_PITCH_NAME_TABLE",
|
|
||||||
"MIDI_PITCHED_NOTE_NAME_GROUP",
|
|
||||||
"MIDI_PITCHED_NOTE_NAME_TABLE",
|
|
||||||
"MIDI_PERCUSSION_NOTE_NAME_TABLE",
|
|
||||||
# Minecraft 自己的对照表
|
|
||||||
"MC_PERCUSSION_INSTRUMENT_LIST",
|
|
||||||
"MC_PITCHED_INSTRUMENT_LIST",
|
|
||||||
"MC_INSTRUMENT_BLOCKS_TABLE",
|
|
||||||
"MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE",
|
|
||||||
"MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE",
|
|
||||||
# Midi 与 游戏 的对照表
|
|
||||||
"MM_INSTRUMENT_RANGE_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",
|
|
||||||
# 操作性函数
|
|
||||||
"velocity_2_distance_natural",
|
|
||||||
"velocity_2_distance_straight",
|
|
||||||
"panning_2_rotation_linear",
|
|
||||||
"panning_2_rotation_trigonometric",
|
|
||||||
# 工具函数
|
|
||||||
"load_decode_musicsequence_metainfo",
|
|
||||||
"load_decode_msq_flush_release",
|
|
||||||
"load_decode_fsq_flush_release",
|
|
||||||
"guess_deviation",
|
|
||||||
"mctick2timestr",
|
|
||||||
"midi_inst_to_mc_sound",
|
|
||||||
]
|
|
||||||
|
|
||||||
from .main import MusicSequence, MidiConvert
|
from .paramcurve import ParamCurve, InterpolationMethod, BoundaryBehaviour
|
||||||
|
|
||||||
from .subclass import (
|
from .data import (
|
||||||
|
SingleMusic,
|
||||||
|
SingleTrack,
|
||||||
|
SingleNote,
|
||||||
|
SoundAtmos,
|
||||||
MineNote,
|
MineNote,
|
||||||
MineCommand,
|
CurvableParam,
|
||||||
SingleNoteBox,
|
|
||||||
ProgressBarStyle,
|
|
||||||
mctick2timestr,
|
|
||||||
DEFAULT_PROGRESSBAR_STYLE,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .utils import (
|
from .plugins import load_plugin_module
|
||||||
# 兼容性函数
|
|
||||||
load_decode_musicsequence_metainfo,
|
|
||||||
load_decode_msq_flush_release,
|
|
||||||
load_decode_fsq_flush_release,
|
|
||||||
# 工具函数
|
|
||||||
guess_deviation,
|
|
||||||
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 .main import MusiCreater
|
||||||
# 字典键
|
|
||||||
MIDI_PROGRAM,
|
__all__ = [
|
||||||
MIDI_PAN,
|
"__version__",
|
||||||
MIDI_VOLUME,
|
"__author__",
|
||||||
# 默认值
|
# 参数曲线相关
|
||||||
MIDI_DEFAULT_PROGRAM_VALUE,
|
"ParamCurve",
|
||||||
MIDI_DEFAULT_VOLUME_VALUE,
|
"InterpolationMethod",
|
||||||
# MIDI 表
|
"BoundaryBehaviour",
|
||||||
MIDI_PITCH_NAME_TABLE,
|
# 音乐数据结构
|
||||||
MIDI_PITCHED_NOTE_NAME_GROUP,
|
"SingleMusic",
|
||||||
MIDI_PITCHED_NOTE_NAME_TABLE,
|
"SingleTrack",
|
||||||
MIDI_PERCUSSION_NOTE_NAME_TABLE,
|
"SingleNote",
|
||||||
# 我的世界 表
|
"SoundAtmos",
|
||||||
MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE,
|
"MineNote",
|
||||||
MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
|
"CurvableParam",
|
||||||
MC_INSTRUMENT_BLOCKS_TABLE,
|
# 工程项目相关
|
||||||
MC_PERCUSSION_INSTRUMENT_LIST,
|
"load_plugin_module",
|
||||||
MC_PITCHED_INSTRUMENT_LIST,
|
"MusiCreater",
|
||||||
# MIDI 到 我的世界 表
|
]
|
||||||
MM_INSTRUMENT_RANGE_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,
|
|
||||||
)
|
|
||||||
|
|||||||
634
Musicreater/_plugin_abc.py
Normal file
634
Musicreater/_plugin_abc.py
Normal file
@@ -0,0 +1,634 @@
|
|||||||
|
# -*- 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
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# NOTE: [WARNING]
|
||||||
|
# 这个文件是一坨屎山代码
|
||||||
|
# 请勿模仿,请多包容
|
||||||
|
# =====================
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import (
|
||||||
|
Dict,
|
||||||
|
Any,
|
||||||
|
Optional,
|
||||||
|
List,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
Sequence,
|
||||||
|
BinaryIO,
|
||||||
|
Generator,
|
||||||
|
Iterator,
|
||||||
|
Set,
|
||||||
|
Type,
|
||||||
|
Mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11):
|
||||||
|
import tomllib
|
||||||
|
import tomli_w
|
||||||
|
else:
|
||||||
|
import tomli as tomllib # 第三方包
|
||||||
|
import tomli_w
|
||||||
|
|
||||||
|
from .exceptions import (
|
||||||
|
PluginConfigDumpError,
|
||||||
|
PluginConfigLoadError,
|
||||||
|
PluginMetainfoNotFoundError,
|
||||||
|
PluginMetainfoTypeError,
|
||||||
|
PluginMetainfoValueError,
|
||||||
|
PluginAttributeNotFoundError,
|
||||||
|
ParameterTypeError,
|
||||||
|
PluginInstanceNotFoundError,
|
||||||
|
)
|
||||||
|
from .data import SingleMusic, SingleTrack
|
||||||
|
|
||||||
|
# 已经全部由 plugins.py 提供接口
|
||||||
|
# 请用户从 plugins.py 导入
|
||||||
|
# 不要在这里导,会坏掉的
|
||||||
|
|
||||||
|
# __all__ = [
|
||||||
|
# # 枚举类
|
||||||
|
# "PluginType",
|
||||||
|
# # 抽象基类/数据类(插件参数定义)
|
||||||
|
# "PluginConfig",
|
||||||
|
# "PluginMetaInformation",
|
||||||
|
# # 抽象基类(插件定义)
|
||||||
|
# "MusicInputPlugin",
|
||||||
|
# "TrackInputPlugin",
|
||||||
|
# "MusicOperatePlugin",
|
||||||
|
# "TrackOperatePlugin",
|
||||||
|
# "MusicOutputPlugin",
|
||||||
|
# "TrackOutputPlugin",
|
||||||
|
# "ServicePlugin",
|
||||||
|
# "LibraryPlugin",
|
||||||
|
# # 插件注册用装饰函数
|
||||||
|
# "music_input_plugin",
|
||||||
|
# "track_input_plugin",
|
||||||
|
# "music_operate_plugin",
|
||||||
|
# "track_operate_plugin",
|
||||||
|
# "music_output_plugin",
|
||||||
|
# "track_output_plugin",
|
||||||
|
# "service_plugin",
|
||||||
|
# "library_plugin",
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# 枚举类
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
|
||||||
|
class PluginTypes(str, Enum):
|
||||||
|
"""插件类型枚举"""
|
||||||
|
|
||||||
|
FUNCTION_MUSIC_IMPORT = "import_music_data"
|
||||||
|
FUNCTION_TRACK_IMPORT = "import_track_data"
|
||||||
|
FUNCTION_MUSIC_OPERATE = "music_data_operating"
|
||||||
|
FUNCTION_TRACK_OPERATE = "track_data_operating"
|
||||||
|
FUNCTION_MUSIC_EXPORT = "export_music_data"
|
||||||
|
FUNCTION_TRACK_EXPORT = "export_track_data"
|
||||||
|
SERVICE = "service"
|
||||||
|
LIBRARY = "library"
|
||||||
|
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# 数据类
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PluginConfig(ABC):
|
||||||
|
"""插件配置基类"""
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
"""将配置内容转换为字典
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
Dict[str, Any]
|
||||||
|
配置项的字典表示,不包含以下划线开头的私有属性
|
||||||
|
"""
|
||||||
|
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: Mapping[str, Any]) -> "PluginConfig":
|
||||||
|
"""从字典创建配置实例
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: Dict[str, Any]
|
||||||
|
包含配置字段的字典
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
PluginConfig
|
||||||
|
配置类的实例
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 只保留类中定义的字段
|
||||||
|
field_names = {f.name for f in cls.__dataclass_fields__.values()}
|
||||||
|
filtered_data = {k: v for k, v in data.items() if k in field_names}
|
||||||
|
return cls(**filtered_data)
|
||||||
|
|
||||||
|
def save_to_file(self, file_path: Path) -> None:
|
||||||
|
"""保存配置到 TOML 文件
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
目标文件路径;必须以 .toml 为后缀
|
||||||
|
|
||||||
|
异常
|
||||||
|
====
|
||||||
|
PluginConfigDumpError
|
||||||
|
当文件后缀不是 .toml 或写入失败时抛出
|
||||||
|
"""
|
||||||
|
if file_path.suffix.upper() == ".TOML":
|
||||||
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
else:
|
||||||
|
raise PluginConfigDumpError(
|
||||||
|
"插件配置文件类型不应为`{}`,须为`TOML`格式。".format(file_path.suffix)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
tomli_w.dump(self.to_dict(), f, multiline_strings=False, indent=4)
|
||||||
|
except Exception as e:
|
||||||
|
raise PluginConfigDumpError("插件配置文件无法保存。") from e
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_from_file(cls, file_path: Path) -> "PluginConfig":
|
||||||
|
"""从 TOML 文件加载配置
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
源文件路径
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
PluginConfig
|
||||||
|
加载后的配置实例
|
||||||
|
|
||||||
|
异常
|
||||||
|
====
|
||||||
|
PluginConfigLoadError
|
||||||
|
当读取或解析失败时抛出
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return cls.from_dict(tomllib.load(f))
|
||||||
|
except Exception as e:
|
||||||
|
raise PluginConfigLoadError("插件配置文件无法加载。") from e
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PluginMetaInformation(ABC):
|
||||||
|
"""插件元信息"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
"""插件名称,应为惟一之名"""
|
||||||
|
author: str
|
||||||
|
"""插件作者"""
|
||||||
|
description: str
|
||||||
|
"""插件简介"""
|
||||||
|
version: Tuple[int, ...]
|
||||||
|
"""插件版本号"""
|
||||||
|
type: PluginTypes
|
||||||
|
"""插件类型"""
|
||||||
|
license: str = "MIT License"
|
||||||
|
"""插件发布时采用的许可协议"""
|
||||||
|
dependencies: Sequence[str] = tuple()
|
||||||
|
"""插件是否对其他插件存在依赖"""
|
||||||
|
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# 抽象基类
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
|
||||||
|
class TopPluginBase(ABC):
|
||||||
|
"""所有插件的抽象基类"""
|
||||||
|
|
||||||
|
metainfo: PluginMetaInformation
|
||||||
|
"""插件元信息"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
if hasattr(cls, "metainfo"):
|
||||||
|
if not isinstance(cls.metainfo, PluginMetaInformation):
|
||||||
|
raise PluginMetainfoTypeError(
|
||||||
|
"类`{cls_name}`之属性`metainfo`的类型,必须为`PluginMetaInformation`".format(
|
||||||
|
cls_name=cls.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if not cls.__name__.endswith("PluginBase"):
|
||||||
|
raise PluginMetainfoNotFoundError(
|
||||||
|
"类`{cls_name}`必须定义一个`metainfo`属性。".format(
|
||||||
|
cls_name=cls.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TopInOutPluginBase(TopPluginBase, ABC):
|
||||||
|
"""导入导出用抽象基类"""
|
||||||
|
|
||||||
|
supported_formats: Tuple[str, ...] = tuple()
|
||||||
|
"""支持的格式(定义后会自动转大写)"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if hasattr(cls, "supported_formats"):
|
||||||
|
if cls.supported_formats:
|
||||||
|
# 强制转换为大写,并使用元组
|
||||||
|
cls.supported_formats = tuple(map(str.upper, cls.supported_formats))
|
||||||
|
else:
|
||||||
|
cls.supported_formats = tuple()
|
||||||
|
else:
|
||||||
|
raise PluginAttributeNotFoundError(
|
||||||
|
"用于导入导出数据的类`{cls_name}`必须定义一个`supported_formats`属性。".format(
|
||||||
|
cls_name=cls.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def can_handle_file(self, file_path: Path) -> bool:
|
||||||
|
"""判断是否可处理某个文件
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
待检测的文件路径
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
bool
|
||||||
|
若文件后缀已在本类中定义,则返回 True
|
||||||
|
"""
|
||||||
|
return file_path.suffix.upper().endswith(self.supported_formats)
|
||||||
|
|
||||||
|
def can_handle_format(self, format_name: str) -> bool:
|
||||||
|
"""判断是否可处理某个格式
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
format_name: str
|
||||||
|
格式名称(如 'MIDI', 'WAV')
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
bool
|
||||||
|
若格式名本类中已经定义,则返回 True
|
||||||
|
"""
|
||||||
|
return format_name.upper().endswith(self.supported_formats)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicInputPluginBase(TopInOutPluginBase, ABC):
|
||||||
|
"""导入用插件抽象基类-完整曲目"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_IMPORT:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
"""从字节流加载数据到完整曲目
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
bytes_buffer_in: BinaryIO
|
||||||
|
输入的二进制字节流
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleMusic
|
||||||
|
解析得到的完整曲目对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
|
||||||
|
"""从文件加载数据到完整曲目
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
输入文件路径
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleMusic
|
||||||
|
解析得到的完整曲目对象
|
||||||
|
"""
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return self.loadbytes(f, config)
|
||||||
|
|
||||||
|
|
||||||
|
class TrackInputPluginBase(TopInOutPluginBase, ABC):
|
||||||
|
"""导入用插件抽象基类-单个音轨"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_IMPORT:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||||
|
) -> "SingleTrack":
|
||||||
|
"""从字节流加载音符数据到单个音轨
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
bytes_buffer_in: BinaryIO
|
||||||
|
输入的二进制字节流
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleTrack
|
||||||
|
解析得到的单个音轨对象
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack":
|
||||||
|
"""从文件加载音符数据到单个音轨
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
输入文件路径
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleTrack
|
||||||
|
解析得到的单个音轨对象
|
||||||
|
"""
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return self.loadbytes(f, config)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicOperatePluginBase(TopPluginBase, ABC):
|
||||||
|
"""音乐处理用插件抽象基类-完整曲目"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_OPERATE:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def process(
|
||||||
|
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
"""处理完整曲目的数据
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleMusic
|
||||||
|
待处理的完整曲目
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleMusic
|
||||||
|
处理后的完整曲目
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TrackOperatePluginBase(TopPluginBase, ABC):
|
||||||
|
"""音乐处理用插件抽象基类-单个音轨"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_OPERATE:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def process(
|
||||||
|
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||||
|
) -> "SingleTrack":
|
||||||
|
"""处理单个音轨的音符数据
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleTrack
|
||||||
|
待处理的单个音轨
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleTrack
|
||||||
|
处理后的单个音轨
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
||||||
|
"""导出用插件的抽象基类-完整曲目"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_EXPORT:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def stream_dump(
|
||||||
|
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
"""将完整曲目导出为对应格式的字节流
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleMusic
|
||||||
|
待导出的完整曲目
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
Iterator[bytes]
|
||||||
|
分块导出的二进制字节串
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def dump(
|
||||||
|
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
||||||
|
):
|
||||||
|
"""将完整曲目导出为对应格式的文件
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleMusic
|
||||||
|
待导出的完整曲目
|
||||||
|
file_path: Path
|
||||||
|
输出文件路径
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
"""
|
||||||
|
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
|
class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
||||||
|
"""导出用插件的抽象基类-单个音轨"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_EXPORT:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def stream_dump(
|
||||||
|
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
"""将单个音轨导出为对应格式的字节流
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleTrack
|
||||||
|
待导出的单个音轨
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
Iterator[bytes]
|
||||||
|
分块导出的二进制字节串
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def dump(
|
||||||
|
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
||||||
|
):
|
||||||
|
"""将单个音轨导出为对应格式的文件
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleTrack
|
||||||
|
待导出的单个音轨
|
||||||
|
file_path: Path
|
||||||
|
输出文件路径
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
"""
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
|
class ServicePluginBase(TopPluginBase, ABC):
|
||||||
|
"""服务插件抽象基类"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.SERVICE:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`ServicePlugin`继承的,该类的子类应当为一个`PluginType.SERVICE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def serve(self, config: Optional[PluginConfig]) -> None:
|
||||||
|
"""服务插件的运行逻辑
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryPluginBase(TopPluginBase, ABC):
|
||||||
|
"""插件依赖库的抽象基类"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.LIBRARY:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 怎么?
|
||||||
|
# 插件的依赖项就不需要什么调用了吧
|
||||||
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_,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
版权所有 © 2026 金羿 & 诸葛亮与八卦阵
|
||||||
Copyright © 2025 Eilles & bgArray
|
Copyright © 2026 Eilles & bgArray
|
||||||
|
|
||||||
开源相关声明请见 仓库根目录下的 License.md
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
Terms & Conditions: License.md in the root directory
|
Terms & Conditions: License.md in the root directory
|
||||||
@@ -34,28 +34,10 @@ 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", # Midi 最低,C-1
|
||||||
1: "C#",
|
1: "C#",
|
||||||
2: "D",
|
2: "D",
|
||||||
3: "D#",
|
3: "D#",
|
||||||
@@ -67,7 +49,7 @@ MIDI_PITCH_NAME_TABLE: Dict[int, str] = {
|
|||||||
9: "A",
|
9: "A",
|
||||||
10: "A#",
|
10: "A#",
|
||||||
11: "B",
|
11: "B",
|
||||||
12: "C",
|
12: "C", # C0
|
||||||
13: "C#",
|
13: "C#",
|
||||||
14: "D",
|
14: "D",
|
||||||
15: "D#",
|
15: "D#",
|
||||||
@@ -76,10 +58,10 @@ MIDI_PITCH_NAME_TABLE: Dict[int, str] = {
|
|||||||
18: "F#",
|
18: "F#",
|
||||||
19: "G",
|
19: "G",
|
||||||
20: "G#",
|
20: "G#",
|
||||||
21: "A",
|
21: "A", # 钢琴最低,A0
|
||||||
22: "A#",
|
22: "A#",
|
||||||
23: "B",
|
23: "B",
|
||||||
24: "C",
|
24: "C", # C1
|
||||||
25: "C#",
|
25: "C#",
|
||||||
26: "D",
|
26: "D",
|
||||||
27: "D#",
|
27: "D#",
|
||||||
@@ -115,7 +97,7 @@ MIDI_PITCH_NAME_TABLE: Dict[int, str] = {
|
|||||||
57: "A",
|
57: "A",
|
||||||
58: "A#",
|
58: "A#",
|
||||||
59: "B",
|
59: "B",
|
||||||
60: "C",
|
60: "C", # 钢琴中央 C,C4
|
||||||
61: "C#",
|
61: "C#",
|
||||||
62: "D",
|
62: "D",
|
||||||
63: "D#",
|
63: "D#",
|
||||||
@@ -163,7 +145,7 @@ MIDI_PITCH_NAME_TABLE: Dict[int, str] = {
|
|||||||
105: "A",
|
105: "A",
|
||||||
106: "A#",
|
106: "A#",
|
||||||
107: "B",
|
107: "B",
|
||||||
108: "C",
|
108: "C", # 钢琴最高,C8
|
||||||
109: "C#",
|
109: "C#",
|
||||||
110: "D",
|
110: "D",
|
||||||
111: "D#",
|
111: "D#",
|
||||||
@@ -182,10 +164,11 @@ MIDI_PITCH_NAME_TABLE: Dict[int, str] = {
|
|||||||
124: "E",
|
124: "E",
|
||||||
125: "F",
|
125: "F",
|
||||||
126: "F#",
|
126: "F#",
|
||||||
127: "G",
|
127: "G", # G9
|
||||||
}
|
}
|
||||||
"""Midi音高名称对照表"""
|
"""Midi音高名称对照表"""
|
||||||
|
|
||||||
|
|
||||||
MIDI_PITCHED_NOTE_NAME_GROUP: Dict[int, Tuple[str, str]] = {
|
MIDI_PITCHED_NOTE_NAME_GROUP: Dict[int, Tuple[str, str]] = {
|
||||||
1: ("钢琴", "Piano"),
|
1: ("钢琴", "Piano"),
|
||||||
9: ("半音阶打击乐器", "Chromatic Percussion"),
|
9: ("半音阶打击乐器", "Chromatic Percussion"),
|
||||||
@@ -528,770 +511,9 @@ 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,14 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储音·创新数据存储类
|
音·创 v3 的内部数据类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# WARNING 本文件中使用之功能尚未启用
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
版权所有 © 2025 金羿
|
版权所有 © 2026 金羿
|
||||||
Copyright © 2025 Eilles
|
Copyright © 2026 Eilles
|
||||||
|
|
||||||
开源相关声明请见 仓库根目录下的 License.md
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
Terms & Conditions: License.md in the root directory
|
Terms & Conditions: License.md in the root directory
|
||||||
@@ -18,54 +16,71 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
# “
|
||||||
|
# 把代码 洒落在这里
|
||||||
|
# 和音符 留下的沙砾
|
||||||
|
# 一点一点爬进你类定义的缝隙
|
||||||
|
# ” —— 乐曲访问 by resnah
|
||||||
|
|
||||||
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf
|
import heapq
|
||||||
|
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf, ceil
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Any, List, Tuple, Union, Dict, Sequence, Callable
|
from typing import (
|
||||||
import bisect
|
Optional,
|
||||||
|
Any,
|
||||||
|
List,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
Dict,
|
||||||
|
Set,
|
||||||
|
Sequence,
|
||||||
|
Callable,
|
||||||
|
Generator,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
Literal,
|
||||||
|
Hashable,
|
||||||
|
TypeVar,
|
||||||
|
Mapping,
|
||||||
|
)
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from .types import FittingFunctionType
|
from .exceptions import SingleNoteDecodeError, ParameterTypeError, ParameterValueError
|
||||||
from .constants import MC_PITCHED_INSTRUMENT_LIST
|
from .paramcurve import ParamCurve
|
||||||
|
|
||||||
|
|
||||||
class ArgumentCurve:
|
|
||||||
|
|
||||||
base_line: float = 0
|
|
||||||
"""基线/默认值"""
|
|
||||||
|
|
||||||
default_curve: Callable[[float], float]
|
|
||||||
"""默认曲线"""
|
|
||||||
|
|
||||||
defined_curves: Dict[float, "ArgumentCurve"] = {}
|
|
||||||
"""调整后的曲线集合"""
|
|
||||||
|
|
||||||
left_border: float = 0
|
|
||||||
"""定义域左边界"""
|
|
||||||
|
|
||||||
right_border: float = inf
|
|
||||||
"""定义域右边界"""
|
|
||||||
|
|
||||||
def __init__(self, baseline: float = 0, default_function: Callable[[float], float] = lambda x: 0, function_set: Dict = {}) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class SoundAtmos:
|
class SoundAtmos:
|
||||||
|
"""声源方位类"""
|
||||||
|
|
||||||
sound_distance: float
|
sound_distance: float
|
||||||
"""声源距离 方块"""
|
"""声源距离 方块"""
|
||||||
|
|
||||||
sound_azimuth: Tuple[float, float]
|
sound_azimuth: Tuple[float, float]
|
||||||
"""声源方位 角度"""
|
"""声源方位 角度(rV左右 rH上下)"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
distance: Optional[float] = None,
|
distance: Optional[float] = None,
|
||||||
azimuth: Optional[Tuple[float, float]] = None,
|
azimuth: Optional[Tuple[float, float]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
定义一个发声方位
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
distance: float
|
||||||
|
发声源距离玩家的距离(半径 `r`)
|
||||||
|
注:距离越近,音量越高,默认为 0。此参数可以作为音轨的音量使用。
|
||||||
|
音量若默认为 +0,则此值当为 8;此值最小为 0.01,最大为 16。
|
||||||
|
azimuth: tuple[float, float]
|
||||||
|
声源方位
|
||||||
|
注:此参数为tuple,包含两个元素,分别表示:
|
||||||
|
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
||||||
|
`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)
|
||||||
"""声源方位"""
|
"""声源方位"""
|
||||||
@@ -103,7 +118,7 @@ class SoundAtmos:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def position_displacement(self) -> Tuple[float, float, float]:
|
def position_displacement(self) -> Tuple[float, float, float]:
|
||||||
"""声像位移"""
|
"""声像位移,直接可应用于我的世界的相对视角的坐标参考系中(^x ^y ^z)"""
|
||||||
dk1 = self.sound_distance * round(cos(radians(self.sound_azimuth[1])), 8)
|
dk1 = self.sound_distance * round(cos(radians(self.sound_azimuth[1])), 8)
|
||||||
return (
|
return (
|
||||||
-dk1 * round(sin(radians(self.sound_azimuth[0])), 8),
|
-dk1 * round(sin(radians(self.sound_azimuth[0])), 8),
|
||||||
@@ -111,24 +126,29 @@ 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_tick: int
|
start_time: int
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
|
|
||||||
duration: int
|
duration: int
|
||||||
"""音符持续时间 命令刻"""
|
"""音符持续时间 命令刻"""
|
||||||
|
|
||||||
high_precision_time: int
|
high_precision_start_time: int
|
||||||
"""高精度开始时间偏量 1/1250 秒"""
|
"""高精度开始时间偏量 1/1250 秒"""
|
||||||
|
|
||||||
extra_info: Dict[str, Any]
|
extra_info: Dict[str, Any]
|
||||||
@@ -136,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] = {},
|
||||||
):
|
):
|
||||||
@@ -149,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
|
||||||
开始之时(命令刻)
|
开始之时(命令刻)
|
||||||
注:此处的时间是用从乐曲开始到当前的刻数
|
注:此处的时间是用从乐曲开始到当前的刻数
|
||||||
@@ -161,14 +181,6 @@ class SingleNote:
|
|||||||
高精度的开始时间偏移量(1/1250秒)
|
高精度的开始时间偏移量(1/1250秒)
|
||||||
is_percussion: bool
|
is_percussion: bool
|
||||||
是否作为打击乐器
|
是否作为打击乐器
|
||||||
distance: float
|
|
||||||
发声源距离玩家的距离(半径 `r`)
|
|
||||||
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
|
|
||||||
azimuth: tuple[float, float]
|
|
||||||
声源方位
|
|
||||||
注:此参数为tuple,包含两个元素,分别表示:
|
|
||||||
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
|
||||||
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
|
||||||
extra_information: Dict[str, Any]
|
extra_information: Dict[str, Any]
|
||||||
附加信息,尽量存储为字典
|
附加信息,尽量存储为字典
|
||||||
|
|
||||||
@@ -177,15 +189,15 @@ 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_tick: int = start_time
|
self.start_time: int = start_tick
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
self.duration: int = last_time
|
self.duration: int = keep_tick
|
||||||
"""音符持续时间 命令刻"""
|
"""音符持续时间 命令刻"""
|
||||||
self.high_precision_time: int = mass_precision_time
|
self.high_precision_start_time: int = mass_precision_time
|
||||||
"""高精度开始时间偏量 0.4 毫秒"""
|
"""高精度开始时间偏量 0.4 毫秒"""
|
||||||
|
|
||||||
self.extra_info = extra_information if extra_information else {}
|
self.extra_info = extra_information if extra_information else {}
|
||||||
@@ -197,24 +209,31 @@ 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:
|
except Exception as e:
|
||||||
|
# 我也不知道为什么这里要放一个异常处理
|
||||||
|
# 之前用到过吗?
|
||||||
|
# —— 2026.01.25 金羿
|
||||||
print(
|
print(
|
||||||
"[Error] 单音符解析错误,字节码`{}`,{}启用高精度时间偏移\n".format(
|
"[Exception] 单音符解析错误,字节码`{}`,{}启用高精度时间偏移\n".format(
|
||||||
code_buffer, "已" if is_high_time_precision else "未"
|
code_buffer, "已" if is_high_time_precision else "未"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
raise
|
raise SingleNoteDecodeError(
|
||||||
|
"技术信息:\nGROUP1\t`{}`\nCODE_BUFFER\t`{}`".format(
|
||||||
|
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:
|
||||||
"""
|
"""
|
||||||
@@ -234,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 字节
|
||||||
@@ -244,14 +263,14 @@ class SingleNote:
|
|||||||
return (
|
return (
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
((((self.note_pitch << 7) + self.velocity) << 17) + self.start_tick)
|
((((self.midi_pitch << 7) + self.volume) << 17) + self.start_time)
|
||||||
<< 17
|
<< 17
|
||||||
)
|
)
|
||||||
+ self.duration
|
+ self.duration
|
||||||
).to_bytes(6, "big")
|
).to_bytes(6, "big")
|
||||||
# + self.track_no.to_bytes(1, "big")
|
# + self.track_no.to_bytes(1, "big")
|
||||||
+ (
|
+ (
|
||||||
self.high_precision_time.to_bytes(1, "big")
|
self.high_precision_start_time.to_bytes(1, "big")
|
||||||
if is_high_time_precision
|
if is_high_time_precision
|
||||||
else b""
|
else b""
|
||||||
)
|
)
|
||||||
@@ -270,19 +289,21 @@ class SingleNote:
|
|||||||
self.extra_info[key[i]] = value[i]
|
self.extra_info[key[i]] = value[i]
|
||||||
else:
|
else:
|
||||||
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
||||||
raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
|
raise ParameterTypeError(
|
||||||
|
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||||
|
)
|
||||||
|
|
||||||
def get_info(self, key: str, default: Any = None) -> Any:
|
def get_info(self, key: str, default: Any = None) -> Any:
|
||||||
"""获取附加信息"""
|
"""获取附加信息"""
|
||||||
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_tick,
|
self.start_time,
|
||||||
self.duration,
|
self.duration,
|
||||||
self.high_precision_time,
|
self.high_precision_start_time,
|
||||||
) + (
|
) + (
|
||||||
", ExtraData = {})".format(self.extra_info) if include_extra_data else ")"
|
", ExtraData = {})".format(self.extra_info) if include_extra_data else ")"
|
||||||
)
|
)
|
||||||
@@ -294,55 +315,118 @@ 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_tick,
|
self.start_time,
|
||||||
self.duration,
|
self.duration,
|
||||||
self.high_precision_time,
|
self.high_precision_start_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
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_tick,
|
"StartTick": self.start_time,
|
||||||
"Duration": self.duration,
|
"Duration": self.duration,
|
||||||
"TimeOffset": self.high_precision_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_tick == other.start_tick:
|
if self.start_time == other.start_time:
|
||||||
return self.high_precision_time < other.high_precision_time
|
return self.high_precision_start_time < other.high_precision_start_time
|
||||||
else:
|
else:
|
||||||
return self.start_tick < other.start_tick
|
return self.start_time < other.start_time
|
||||||
|
|
||||||
def __gt__(self, other) -> bool:
|
def __gt__(self, other: "SingleNote") -> bool:
|
||||||
"""比较自己是否在开始时间上晚于另一个音符"""
|
"""比较自己是否在开始时间上晚于另一个音符"""
|
||||||
if self.start_tick == other.start_tick:
|
if self.start_time == other.start_time:
|
||||||
return self.high_precision_time > other.high_precision_time
|
return self.high_precision_start_time > other.high_precision_start_time
|
||||||
else:
|
else:
|
||||||
return self.start_tick > other.start_tick
|
return self.start_time > other.start_time
|
||||||
|
|
||||||
|
|
||||||
class SingleTrack(list):
|
class CurvableParam(str, Enum):
|
||||||
|
"""可曲线化的参数 枚举类"""
|
||||||
|
|
||||||
|
PITCH = "adjust_note_pitch"
|
||||||
|
VOLUME = "adjust_note_volume"
|
||||||
|
DISTANCE = "adjust_note_sound_distance"
|
||||||
|
LR_PANNING = "adjust_note_leftright_panning_degree"
|
||||||
|
UD_PANNING = "adjust_note_updown_panning_degree"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MineNote:
|
||||||
|
"""我的世界音符对象(仅提供我的世界相关接口)"""
|
||||||
|
|
||||||
|
pitch: float
|
||||||
|
"""Midi 音高"""
|
||||||
|
instrument: str
|
||||||
|
"""乐器 ID"""
|
||||||
|
volume: float
|
||||||
|
"""力度/播放音量 0~127 百廿七分比"""
|
||||||
|
start_tick: int
|
||||||
|
"""开始之时 命令刻"""
|
||||||
|
duration_tick: int
|
||||||
|
"""音符持续时间 命令刻"""
|
||||||
|
persiced_time: int
|
||||||
|
"""高精度开始时间偏量 1/1250 秒"""
|
||||||
|
percussive: bool
|
||||||
|
"""是否作为打击乐器启用"""
|
||||||
|
position: SoundAtmos
|
||||||
|
"""声像方位"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_single_note(
|
||||||
|
cls,
|
||||||
|
note: SingleNote,
|
||||||
|
note_instrument: str,
|
||||||
|
is_persiced_time: bool,
|
||||||
|
is_percussive_note: bool,
|
||||||
|
sound_position: SoundAtmos,
|
||||||
|
adjust_note_pitch: float = 0.0,
|
||||||
|
adjust_note_volume: float = 0.0,
|
||||||
|
adjust_note_sound_distance: float = 0.0,
|
||||||
|
adjust_note_leftright_panning_degree: float = 0.0,
|
||||||
|
adjust_note_updown_panning_degree: float = 0.0,
|
||||||
|
) -> "MineNote":
|
||||||
|
"""从SingleNote对象创建MineNote对象"""
|
||||||
|
sound_position.sound_distance += adjust_note_sound_distance
|
||||||
|
sound_position.sound_azimuth = (
|
||||||
|
sound_position.sound_azimuth[0] + adjust_note_leftright_panning_degree,
|
||||||
|
sound_position.sound_azimuth[1] + adjust_note_updown_panning_degree,
|
||||||
|
)
|
||||||
|
return cls(
|
||||||
|
pitch=note.midi_pitch + adjust_note_pitch,
|
||||||
|
instrument=note_instrument,
|
||||||
|
volume=note.volume + adjust_note_volume,
|
||||||
|
start_tick=note.start_time,
|
||||||
|
duration_tick=note.duration,
|
||||||
|
persiced_time=note.high_precision_start_time if is_persiced_time else 0,
|
||||||
|
percussive=is_percussive_note,
|
||||||
|
position=sound_position,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SingleTrack(List[SingleNote]):
|
||||||
"""存储单个轨道的类"""
|
"""存储单个轨道的类"""
|
||||||
|
|
||||||
track_name: str
|
name: str
|
||||||
"""轨道之名称"""
|
"""轨道之名称"""
|
||||||
|
|
||||||
track_instrument: str
|
is_enabled: bool = True
|
||||||
"""乐器ID"""
|
"""该音轨是否启用"""
|
||||||
|
|
||||||
track_volume: float
|
instrument: str
|
||||||
"""该音轨的音量"""
|
"""乐器ID"""
|
||||||
|
|
||||||
is_high_time_precision: bool
|
is_high_time_precision: bool
|
||||||
"""该音轨是否使用高精度时间"""
|
"""该音轨是否使用高精度时间"""
|
||||||
@@ -353,7 +437,7 @@ class SingleTrack(list):
|
|||||||
sound_position: SoundAtmos
|
sound_position: SoundAtmos
|
||||||
"""声像方位"""
|
"""声像方位"""
|
||||||
|
|
||||||
argument_curves: Dict[str, FittingFunctionType]
|
argument_curves: Dict[CurvableParam, Union[ParamCurve, Literal[None]]]
|
||||||
"""参数曲线"""
|
"""参数曲线"""
|
||||||
|
|
||||||
extra_info: Dict[str, Any]
|
extra_info: Dict[str, Any]
|
||||||
@@ -361,42 +445,158 @@ class SingleTrack(list):
|
|||||||
|
|
||||||
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 {}
|
||||||
|
|
||||||
|
self.argument_curves = {item: None for item in CurvableParam}
|
||||||
|
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
super().sort()
|
||||||
|
|
||||||
|
def disable(self) -> None:
|
||||||
|
"""禁用音轨"""
|
||||||
|
|
||||||
|
self.is_enabled = False
|
||||||
|
|
||||||
|
def enable(self) -> None:
|
||||||
|
"""启用音轨"""
|
||||||
|
|
||||||
|
self.is_enabled = True
|
||||||
|
|
||||||
|
def toggle_able(self) -> None:
|
||||||
|
"""切换音轨的启用状态"""
|
||||||
|
|
||||||
|
self.is_enabled = not self.is_enabled
|
||||||
|
|
||||||
|
def append(self, object: SingleNote) -> None:
|
||||||
|
"""
|
||||||
|
添加一个音符,推荐使用 add 方法
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.add(object)
|
||||||
|
|
||||||
|
def add(self, item: SingleNote) -> None:
|
||||||
|
"""
|
||||||
|
在音轨里添加一个音符
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(item, SingleNote):
|
||||||
|
raise ParameterTypeError(
|
||||||
|
"单音轨类的元素类型须为单音符(`SingleNote`),不可为:`{}`".format(
|
||||||
|
type(item).__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
super().append(item)
|
||||||
|
super().sort() # =========================== TODO 需要优化
|
||||||
|
|
||||||
|
def update(self, items: Iterable[SingleNote]):
|
||||||
|
"""
|
||||||
|
拼接两个音轨
|
||||||
|
"""
|
||||||
|
super().extend(items)
|
||||||
|
super().sort() # =========================== TODO 需要优化
|
||||||
|
|
||||||
|
def get(self, time: int) -> Generator[SingleNote, None, None]:
|
||||||
|
"""通过开始时间来获取音符"""
|
||||||
|
|
||||||
|
return (x for x in self if x.start_time == time)
|
||||||
|
|
||||||
|
def get_notes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Iterator[SingleNote]:
|
||||||
|
"""通过开始时间和结束时间来获取音符"""
|
||||||
|
if end_time < start_time:
|
||||||
|
raise ParameterValueError(
|
||||||
|
"获取音符的时间范围有误,终止时间`{}`早于起始时间`{}`".format(
|
||||||
|
end_time, start_time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif end_time < 0:
|
||||||
|
raise ParameterValueError(
|
||||||
|
"获取音符的时间范围有误,终止时间`{}`不可为负数".format(end_time)
|
||||||
|
)
|
||||||
|
elif start_time <= 0 and end_time >= self[-1].start_time:
|
||||||
|
return iter(self)
|
||||||
|
|
||||||
|
return (
|
||||||
|
x
|
||||||
|
for x in self
|
||||||
|
if (x.start_time >= start_time) and (x.start_time <= end_time)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_minenotes(
|
||||||
|
self, range_start_time: float = 0, range_end_time: float = inf
|
||||||
|
) -> Generator[MineNote, Any, None]:
|
||||||
|
"""获取能够用以在我的世界播放的音符数据类"""
|
||||||
|
|
||||||
|
for _note in self.get_notes(range_start_time, range_end_time):
|
||||||
|
yield 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])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def note_amount(self) -> int:
|
def note_amount(self) -> int:
|
||||||
"""音符数"""
|
"""音符数"""
|
||||||
return len(self)
|
return len(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notes(self) -> List[SingleNote]:
|
||||||
|
"""音符列表"""
|
||||||
|
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):
|
||||||
@@ -410,12 +610,194 @@ class SingleTrack(list):
|
|||||||
self.extra_info[key[i]] = value[i]
|
self.extra_info[key[i]] = value[i]
|
||||||
else:
|
else:
|
||||||
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
||||||
raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
|
raise ParameterTypeError(
|
||||||
|
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||||
|
)
|
||||||
|
|
||||||
def get_info(self, key: str, default: Any = None) -> Any:
|
def get_info(self, key: str, default: Any = None) -> Any:
|
||||||
"""获取附加信息"""
|
"""获取附加信息"""
|
||||||
return self.extra_info.get(key, default)
|
return self.extra_info.get(key, default)
|
||||||
|
|
||||||
class SingleMusic(object):
|
|
||||||
|
class SingleMusic(List[SingleTrack]):
|
||||||
"""存储单个曲子的类"""
|
"""存储单个曲子的类"""
|
||||||
|
|
||||||
|
music_name: str
|
||||||
|
"""乐曲名称"""
|
||||||
|
|
||||||
|
music_creator: str
|
||||||
|
"""本我的世界曲目的制作者"""
|
||||||
|
|
||||||
|
music_original_author: str
|
||||||
|
"""曲目的原作者"""
|
||||||
|
|
||||||
|
music_description: str
|
||||||
|
"""当前曲目的简介"""
|
||||||
|
|
||||||
|
music_credits: str
|
||||||
|
"""曲目的版权信息"""
|
||||||
|
|
||||||
|
# 感叹一下什么叫冗余设计啊!(叉腰)
|
||||||
|
extra_info: Dict[str, Any]
|
||||||
|
"""这还得放东西?"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args: SingleTrack,
|
||||||
|
name: str = "未命名乐曲",
|
||||||
|
creator: str = "未命名制作者",
|
||||||
|
original_author: str = "未命名原曲作",
|
||||||
|
description: str = "无简介",
|
||||||
|
credits: str = "保留所有权利。All Rights Reserved.",
|
||||||
|
extra_information: Dict[str, Any] = {},
|
||||||
|
):
|
||||||
|
self.music_name = name
|
||||||
|
"""乐曲名称"""
|
||||||
|
|
||||||
|
self.music_creator = creator
|
||||||
|
"""曲目制作者"""
|
||||||
|
|
||||||
|
self.music_original_author = original_author
|
||||||
|
"""乐曲原作者"""
|
||||||
|
|
||||||
|
self.music_description = description
|
||||||
|
"""简介"""
|
||||||
|
|
||||||
|
self.music_credits = credits
|
||||||
|
"""版权信息"""
|
||||||
|
|
||||||
|
self.extra_info = extra_information if extra_information else {}
|
||||||
|
|
||||||
|
super().__init__(*args)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def track_amount(self) -> int:
|
||||||
|
"""音轨数"""
|
||||||
|
return len(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def music_tracks(self) -> Iterator[SingleTrack]:
|
||||||
|
"""音轨列表,不包含被禁用的音轨"""
|
||||||
|
return (track for track in self if track.is_enabled)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def yield_from_tracks(
|
||||||
|
tracks: Sequence[Iterator[T]],
|
||||||
|
sort_key: Callable[[T], Any],
|
||||||
|
is_subseq_sorted: bool = True,
|
||||||
|
) -> Iterator[T]:
|
||||||
|
"""从任意迭代器列表迭代符合顺序的元素
|
||||||
|
(惰性多路归并多个迭代器,按 sort_key 排序)
|
||||||
|
|
||||||
|
参数
|
||||||
|
----
|
||||||
|
tracks: Sequence[Iterator[T]]
|
||||||
|
迭代器列表
|
||||||
|
sort_key: Callable[[T], Any]
|
||||||
|
接受 T 元素,返回可比较的键
|
||||||
|
is_subseq_sorted: bool = True
|
||||||
|
子序列是否已排序
|
||||||
|
|
||||||
|
迭代
|
||||||
|
----
|
||||||
|
归并后的每个元素,按 sort_key 升序
|
||||||
|
"""
|
||||||
|
if is_subseq_sorted:
|
||||||
|
# 必须这样处理,不能 return 这个 merge,测试过了
|
||||||
|
yield from heapq.merge(*tracks, key=sort_key)
|
||||||
|
else:
|
||||||
|
# 初始化堆
|
||||||
|
heap_pool: List[Tuple[Any, int, T]] = []
|
||||||
|
for _index, _track in enumerate(tracks):
|
||||||
|
try:
|
||||||
|
item = next(_track)
|
||||||
|
heapq.heappush(heap_pool, (sort_key(item), _index, item))
|
||||||
|
except StopIteration:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 归并主循环
|
||||||
|
while heap_pool:
|
||||||
|
_key, _index, item = heapq.heappop(heap_pool)
|
||||||
|
yield item
|
||||||
|
try:
|
||||||
|
next_item = next(tracks[_index])
|
||||||
|
heapq.heappush(heap_pool, (sort_key(next_item), _index, next_item))
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
# NEVER REACH:
|
||||||
|
# pool: List[Tuple[str, T]] = []
|
||||||
|
# remove_track: List[str] = []
|
||||||
|
# for _name, _track in tracks.items():
|
||||||
|
# try:
|
||||||
|
# pool.append((_name, next(_track)))
|
||||||
|
# except StopIteration:
|
||||||
|
# remove_track.append(_name)
|
||||||
|
# for _x in remove_track:
|
||||||
|
# tracks.pop(_x)
|
||||||
|
# del remove_track
|
||||||
|
# while tracks and pool:
|
||||||
|
# yield (_x := min(pool, key=sort_key))[1]
|
||||||
|
# try:
|
||||||
|
# pool.append((_x[0], next(tracks[_x[0]])))
|
||||||
|
# except StopIteration:
|
||||||
|
# tracks.pop(_x[0])
|
||||||
|
# pool.sort(key=sort_key)
|
||||||
|
# for _remain in pool:
|
||||||
|
# yield _remain[1]
|
||||||
|
|
||||||
|
def get_tracked_notes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Generator[Iterator[SingleNote], Any, None]:
|
||||||
|
"""获取指定时间段的各个音轨的音符数据"""
|
||||||
|
return (track.get_notes(start_time, end_time) for track in self.music_tracks)
|
||||||
|
|
||||||
|
def get_tracked_minenotes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Generator[Iterator[MineNote], Any, None]:
|
||||||
|
"""获取指定时间段的各个音轨的,供我的世界播放的音符数据类"""
|
||||||
|
return (
|
||||||
|
track.get_minenotes(start_time, end_time) for track in self.music_tracks
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_notes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Iterator[SingleNote]:
|
||||||
|
"""获取指定时间段的所有音符数据,按照时间顺序"""
|
||||||
|
if self.track_amount == 0:
|
||||||
|
return iter(())
|
||||||
|
return self.yield_from_tracks(
|
||||||
|
[track.get_notes(start_time, end_time) for track in self.music_tracks],
|
||||||
|
sort_key=lambda x: x.start_time,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_minenotes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Iterator[MineNote]:
|
||||||
|
"""获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序"""
|
||||||
|
if self.track_amount == 0:
|
||||||
|
return iter(())
|
||||||
|
return self.yield_from_tracks(
|
||||||
|
[track.get_minenotes(start_time, end_time) for track in self.music_tracks],
|
||||||
|
sort_key=lambda x: x.start_tick,
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||||
|
"""设置附加信息"""
|
||||||
|
if isinstance(key, str):
|
||||||
|
self.extra_info[key] = value
|
||||||
|
elif (
|
||||||
|
isinstance(key, Sequence)
|
||||||
|
and isinstance(value, Sequence)
|
||||||
|
and (k := len(key)) == len(value)
|
||||||
|
):
|
||||||
|
for i in range(k):
|
||||||
|
self.extra_info[key[i]] = value[i]
|
||||||
|
else:
|
||||||
|
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
||||||
|
raise ParameterTypeError(
|
||||||
|
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_info(self, key: str, default: Any = None) -> Any:
|
||||||
|
"""获取附加信息"""
|
||||||
|
return self.extra_info.get(key, default)
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存放一些报错类型
|
音·创 v3 用到的一些报错类型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
版权所有 © 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
|
||||||
@@ -16,147 +16,257 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
# “
|
||||||
|
# There are planty of "exception"s in this library
|
||||||
|
# for I know I will always go with my heart.
|
||||||
|
# ” —— Cyberdevil by resnah
|
||||||
|
|
||||||
class MSCTBaseException(Exception):
|
|
||||||
"""音·创 的所有错误均继承于此"""
|
class MusicreaterBaseException(Exception):
|
||||||
|
"""音·创 v3 的所有错误均继承于此"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""音·创 的所有错误均继承于此"""
|
"""音·创 的所有错误均继承于此"""
|
||||||
super().__init__("音·创", *args)
|
super().__init__("[音·创] - ", *args)
|
||||||
|
|
||||||
def meow(
|
def meow(self):
|
||||||
self,
|
|
||||||
):
|
|
||||||
for i in self.args:
|
for i in self.args:
|
||||||
print(i + "喵!")
|
print(i + "喵~", end=":")
|
||||||
|
|
||||||
def crash_it(self):
|
def crash_it(self):
|
||||||
raise self
|
raise self
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return "".join(self.args)
|
||||||
|
|
||||||
class MidiFormatException(MSCTBaseException):
|
|
||||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
# =====================================
|
||||||
|
# NOTE
|
||||||
|
# 面对用户时候爆出去的我们认为这就是“外部错误”
|
||||||
|
# 如果是在程序内部数据传输等情况下出现的就是“内部错误”
|
||||||
|
# 例如,无法读取文件,这就是一个外部错误
|
||||||
|
# 某个参数的数据类型错误,这就是内部错误
|
||||||
|
# =====================================
|
||||||
|
|
||||||
|
|
||||||
|
class MusicreaterInnerlyError(MusicreaterBaseException):
|
||||||
|
"""内部错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
"""内部错误(面向开发者的报错信息)"""
|
||||||
super().__init__("MIDI 格式错误", *args)
|
super().__init__("内部错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
class MidiDestroyedError(MSCTBaseException):
|
class MusicreaterOuterlyError(MusicreaterBaseException):
|
||||||
"""Midi文件损坏"""
|
"""外部错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Midi文件损坏"""
|
"""外部错误(面向用户的报错信息)"""
|
||||||
super().__init__("MIDI文件损坏:无法读取 MIDI 文件", *args)
|
super().__init__("外部错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
# class MidiUnboundError(MSCTBaseException):
|
class InnerlyParameterError(MusicreaterInnerlyError):
|
||||||
# """未定义Midi对象(无用)"""
|
"""内部传参错误"""
|
||||||
|
|
||||||
# def __init__(self, *args):
|
|
||||||
# """未绑定Midi对象"""
|
|
||||||
# super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args)
|
|
||||||
# 此错误在本版本内已经不再使用
|
|
||||||
|
|
||||||
|
|
||||||
class CommandFormatError(MSCTBaseException, RuntimeError):
|
|
||||||
"""指令格式与目标格式不匹配而引起的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""指令格式与目标格式不匹配而引起的错误"""
|
"""参数错误"""
|
||||||
super().__init__("指令格式不匹配", *args)
|
super().__init__("传参错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
# class CrossNoteError(MidiFormatException):
|
class ParameterTypeError(InnerlyParameterError, TypeError):
|
||||||
# """同通道下同音符交叉出现所产生的错误"""
|
"""参数类型错误"""
|
||||||
|
|
||||||
# def __init__(self, *args):
|
|
||||||
# """同通道下同音符交叉出现所产生的错误"""
|
|
||||||
# super().__init__("同通道下同音符交叉", *args)
|
|
||||||
# 这TM是什么错误?
|
|
||||||
# 我什么时候写的这玩意?
|
|
||||||
# 我哪知道这说的是啥?
|
|
||||||
# !!!
|
|
||||||
# 我知道这是什么了 —— 金羿 2025 0401
|
|
||||||
# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息
|
|
||||||
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
|
||||||
|
|
||||||
|
|
||||||
class NotDefineTempoError(MidiFormatException):
|
|
||||||
"""没有Tempo设定导致时间无法计算的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""没有Tempo设定导致时间无法计算的错误"""
|
"""参数类型错误"""
|
||||||
super().__init__("在曲目开始时没有声明 Tempo(未指定拍长)", *args)
|
super().__init__("参数类型错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
class ChannelOverFlowError(MidiFormatException):
|
class ParameterValueError(InnerlyParameterError, ValueError):
|
||||||
"""一个midi中含有过多的通道"""
|
"""参数值存在错误"""
|
||||||
|
|
||||||
def __init__(self, max_channel=16, *args):
|
|
||||||
"""一个midi中含有过多的通道"""
|
|
||||||
super().__init__("含有过多的通道(数量应≤{})".format(max_channel), *args)
|
|
||||||
|
|
||||||
|
|
||||||
class NotDefineProgramError(MidiFormatException):
|
|
||||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
"""参数其值存在错误"""
|
||||||
super().__init__("未指定演奏乐器", *args)
|
super().__init__("参数数值错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
class NoteOnOffMismatchError(MidiFormatException):
|
class PluginNotSpecifiedError(InnerlyParameterError, LookupError):
|
||||||
"""音符开音和停止不匹配的错误"""
|
"""未指定插件"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""音符开音和停止不匹配的错误"""
|
"""未指定插件"""
|
||||||
super().__init__("音符不匹配", *args)
|
super().__init__("未指定插件:", *args)
|
||||||
|
|
||||||
|
|
||||||
class LyricMismatchError(MSCTBaseException):
|
class OuterlyParameterError(MusicreaterOuterlyError):
|
||||||
"""歌词匹配解析错误"""
|
"""外部参数错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""有可能产生了错误的歌词解析"""
|
"""参数错误"""
|
||||||
super().__init__("歌词解析错误", *args)
|
super().__init__("参数错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
|
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
|
||||||
"""以 0 作为播放速度的错误"""
|
"""以 0 作为播放速度的错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""以 0 作为播放速度的错误"""
|
"""以 0 作为播放速度的错误"""
|
||||||
super().__init__("播放速度为零", *args)
|
super().__init__("播放速度为零:", *args)
|
||||||
|
|
||||||
|
|
||||||
class IllegalMinimumVolumeError(MSCTBaseException, ValueError):
|
class IllegalMinimumVolumeError(OuterlyParameterError, ValueError):
|
||||||
"""最小播放音量有误的错误"""
|
"""最小播放音量有误的错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""最小播放音量错误"""
|
"""最小播放音量错误"""
|
||||||
super().__init__("最小播放音量超出范围", *args)
|
super().__init__("最小播放音量超出范围:", *args)
|
||||||
|
|
||||||
|
|
||||||
class MusicSequenceDecodeError(MSCTBaseException):
|
class FileFormatNotSupportedError(MusicreaterOuterlyError):
|
||||||
"""音乐序列解码错误"""
|
"""不支持的文件格式"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""音乐序列无法正确解码的错误"""
|
"""文件格式不受支持"""
|
||||||
super().__init__("解码音符序列文件时出现问题", *args)
|
super().__init__("不支持的文件格式:", *args)
|
||||||
|
|
||||||
|
|
||||||
class MusicSequenceTypeError(MSCTBaseException):
|
class NoteBinaryDecodeError(MusicreaterOuterlyError):
|
||||||
"""音乐序列类型错误"""
|
"""音乐存储二进制数据解码错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""无法识别音符序列字节码的类型"""
|
"""音乐存储二进制数据无法正确解码"""
|
||||||
super().__init__("错误的音符序列字节类型", *args)
|
super().__init__("解码音乐存储二进制数据时出现问题 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
class MusicSequenceVerificationFailed(MusicSequenceDecodeError):
|
class SingleNoteDecodeError(NoteBinaryDecodeError):
|
||||||
"""音乐序列校验失败"""
|
"""单个音符的二进制数据解码错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""音符序列文件与其校验值不一致"""
|
"""单个音符的二进制数据无法正确解码"""
|
||||||
super().__init__("音符序列文件校验失败", *args)
|
super().__init__("音符解码出错:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NoteBinaryFileTypeError(NoteBinaryDecodeError):
|
||||||
|
"""音乐存储二进制数据的文件类型错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""无法识别音乐存储文件的类型"""
|
||||||
|
super().__init__("无法识别音乐存储文件对应的类型:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NoteBinaryFileVerificationFailed(NoteBinaryDecodeError):
|
||||||
|
"""音乐存储二进制数据校验失败"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音乐存储文件与其校验值不一致"""
|
||||||
|
super().__init__("音乐存储文件校验失败:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginDefineError(MusicreaterInnerlyError):
|
||||||
|
"""插件定义错误(内部相关)"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件本身存在错误"""
|
||||||
|
super().__init__("插件内部错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginInstanceNotFoundError(PluginDefineError, LookupError):
|
||||||
|
"""插件实例未找到"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件实例未找到"""
|
||||||
|
super().__init__("插件实例未找到:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginAttributeNotFoundError(PluginDefineError, AttributeError):
|
||||||
|
"""插件属性定义错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件属性定义错误"""
|
||||||
|
super().__init__("插件类的必要属性不存在:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginMetainfoError(PluginDefineError):
|
||||||
|
"""插件元信息定义错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件元信息定义错误"""
|
||||||
|
super().__init__("插件元信息定义错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginMetainfoTypeError(PluginMetainfoError, TypeError):
|
||||||
|
"""插件元信息定义类型错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件元信息定义类型错误"""
|
||||||
|
super().__init__("插件元信息类型错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginMetainfoValueError(PluginMetainfoError, ValueError):
|
||||||
|
"""插件元信息定义值错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件元信息定义值错误"""
|
||||||
|
super().__init__("插件元信息数值错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginMetainfoNotFoundError(PluginMetainfoError, PluginAttributeNotFoundError):
|
||||||
|
"""插件元信息定义缺少错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件元信息定义缺少错误"""
|
||||||
|
super().__init__("插件元信息未定义:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginLoadError(MusicreaterOuterlyError):
|
||||||
|
"""插件加载错误(外部相关)"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件加载错误"""
|
||||||
|
super().__init__("插件加载错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginDependencyNotFound(PluginLoadError):
|
||||||
|
"""插件依赖未找到"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__("未找到所需的插件依赖:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginNotFoundError(PluginLoadError):
|
||||||
|
"""插件未找到"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件未找到"""
|
||||||
|
super().__init__("无法找到插件:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginRegisteredError(PluginLoadError):
|
||||||
|
"""插件重复注册"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件已被注册注册"""
|
||||||
|
super().__init__("插件重复注册:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginConfigRelatedError(MusicreaterOuterlyError):
|
||||||
|
"""插件配置相关错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件配置相关错误"""
|
||||||
|
super().__init__("插件配置相关错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginConfigLoadError(PluginLoadError, PluginConfigRelatedError):
|
||||||
|
"""插件配置加载错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""配置文件无法加载"""
|
||||||
|
super().__init__("插件配置文件加载错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginConfigDumpError(PluginConfigRelatedError):
|
||||||
|
"""插件配置保存错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""配置文件无法保存"""
|
||||||
|
super().__init__("插件配置文件保存错误:", *args)
|
||||||
|
|||||||
2023
Musicreater/main.py
2023
Musicreater/main.py
File diff suppressed because it is too large
Load Diff
581
Musicreater/paramcurve.py
Normal file
581
Musicreater/paramcurve.py
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
# -*- 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
|
||||||
|
|
||||||
|
|
||||||
|
# WARNING 本文件所含之功能未经完整测试
|
||||||
|
# 鉴于白谭若佬给出的建议:本功能应是处于低优先级开发的
|
||||||
|
# 因此暂时用处不大,可以稍微放一会再进行开发
|
||||||
|
# 目前用人工智能生成了部分代码,只经过简单的测试
|
||||||
|
# 可以等伶伦工作站开发出来后再进行完整的测试
|
||||||
|
|
||||||
|
|
||||||
|
from math import ceil
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional, Any, List, Tuple, Callable
|
||||||
|
from enum import Enum
|
||||||
|
import bisect
|
||||||
|
|
||||||
|
|
||||||
|
def _evaluate_bezier_segment(
|
||||||
|
t0: float,
|
||||||
|
v0: float,
|
||||||
|
t1: float,
|
||||||
|
v1: float,
|
||||||
|
out_tangent: Optional[Tuple[float, float]],
|
||||||
|
in_tangent: Optional[Tuple[float, float]],
|
||||||
|
u: float,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
计算贝塞尔区间 [t0, t1] 在归一化参数 u ∈ [0,1] 处的 y 值。
|
||||||
|
|
||||||
|
控制点:
|
||||||
|
P0 = (t0, v0)
|
||||||
|
P1 = (t0 + out_dt, v0 + out_dv)
|
||||||
|
P2 = (t1 - in_dt, v1 - in_dv) ← 注意:in_tangent 是相对于 t1 的偏移
|
||||||
|
P3 = (t1, v1)
|
||||||
|
"""
|
||||||
|
# 默认控制点:退化为线性
|
||||||
|
p0 = (t0, v0)
|
||||||
|
p3 = (t1, v1)
|
||||||
|
|
||||||
|
if out_tangent is not None:
|
||||||
|
p1 = (t0 + out_tangent[0], v0 + out_tangent[1])
|
||||||
|
else:
|
||||||
|
p1 = p0 # 无出手柄 → 与起点重合
|
||||||
|
|
||||||
|
if in_tangent is not None:
|
||||||
|
p2 = (t1 - in_tangent[0], v1 - in_tangent[1])
|
||||||
|
else:
|
||||||
|
p2 = p3 # 无入手柄 → 与终点重合
|
||||||
|
|
||||||
|
# 三次贝塞尔 y(t)
|
||||||
|
mt = 1.0 - u
|
||||||
|
return mt**3 * p0[1] + 3 * mt**2 * u * p1[1] + 3 * mt * u**2 * p2[1] + u**3 * p3[1]
|
||||||
|
|
||||||
|
|
||||||
|
class InterpolationMethod:
|
||||||
|
"""
|
||||||
|
预定义的标准化插值函数集合。所有函数接受归一化输入 u ∈ [0,1],返回 v ∈ [0,1]。
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def linear(u: float) -> float:
|
||||||
|
"""
|
||||||
|
线性插值。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间,范围 [0, 1]。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值权重,范围 [0, 1]。
|
||||||
|
"""
|
||||||
|
return u
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ease_in_quad(u: float) -> float:
|
||||||
|
"""
|
||||||
|
二次缓入(慢进快出)。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间,范围 [0, 1]。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值权重。
|
||||||
|
"""
|
||||||
|
return u * u
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ease_out_quad(u: float) -> float:
|
||||||
|
"""
|
||||||
|
二次缓出(快进慢出)。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间,范围 [0, 1]。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值权重。
|
||||||
|
"""
|
||||||
|
return 1 - (1 - u) ** 2
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ease_in_out_quad(u: float) -> float:
|
||||||
|
"""
|
||||||
|
二次缓入缓出。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间,范围 [0, 1]。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值权重。
|
||||||
|
"""
|
||||||
|
if u < 0.5:
|
||||||
|
return 2 * u * u
|
||||||
|
else:
|
||||||
|
return 1 - pow(-2 * u + 2, 2) / 2
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hold(u: float) -> float:
|
||||||
|
"""
|
||||||
|
阶梯保持模式占位函数。实际插值逻辑在 ParamCurve.value_at 中特殊处理。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间(忽略)。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
无意义,仅作标识。
|
||||||
|
"""
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Keyframe:
|
||||||
|
"""
|
||||||
|
参数曲线上的一个关键帧,支持完整的入/出切线控制。
|
||||||
|
|
||||||
|
插值优先级:
|
||||||
|
1. 若 use_bezier=True → 使用贝塞尔模式(需 in_tangent / out_tangent)
|
||||||
|
2. 否则 → 使用 out_interp 函数(in_interp 被忽略)
|
||||||
|
"""
|
||||||
|
|
||||||
|
time: float
|
||||||
|
value: float
|
||||||
|
|
||||||
|
# 函数插值模式
|
||||||
|
out_interp: Optional[Callable[[float], float]] = None
|
||||||
|
|
||||||
|
# 贝塞尔模式
|
||||||
|
in_tangent: Optional[Tuple[float, float]] = (
|
||||||
|
None # (dt, dv) ← 相对于自身(负 dt 表示左侧)
|
||||||
|
)
|
||||||
|
out_tangent: Optional[Tuple[float, float]] = (
|
||||||
|
None # (dt, dv) → 相对于自身(正 dt 表示右侧)
|
||||||
|
)
|
||||||
|
use_bezier: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class BoundaryBehaviour(str, Enum):
|
||||||
|
"""
|
||||||
|
边界行为枚举。
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONSTANT = "constant"
|
||||||
|
"""返回默认基线值"""
|
||||||
|
HOLD = "hold"
|
||||||
|
"""保持首/尾关键帧的值"""
|
||||||
|
|
||||||
|
|
||||||
|
class ParamCurve:
|
||||||
|
"""
|
||||||
|
参数曲线类
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
支持动态节点编辑
|
||||||
|
用户通过添加/修改关键帧(时间-值对)来定义曲线,类自动在相邻关键帧之间生成插值段。
|
||||||
|
支持多种插值模式:线性('linear')、平滑缓动('smooth')、保持('hold')或自定义函数。
|
||||||
|
"""
|
||||||
|
|
||||||
|
base_line: float = 0.0
|
||||||
|
"""基线/默认值"""
|
||||||
|
|
||||||
|
base_interpolation_function: Callable[[float], float]
|
||||||
|
"""默认(未指定区间时的)关键帧插值模式"""
|
||||||
|
|
||||||
|
boundary_behaviour: BoundaryBehaviour
|
||||||
|
"""边界行为,控制参数曲线在已定义的范围外的返回值"""
|
||||||
|
|
||||||
|
_keys: List[Keyframe]
|
||||||
|
"""关键帧列表"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
base_value: float = 0.0,
|
||||||
|
default_interpolation_function: Callable[[float], float] = InterpolationMethod.linear,
|
||||||
|
boundary_mode: BoundaryBehaviour = BoundaryBehaviour.CONSTANT,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化参数曲线。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
base_value : float
|
||||||
|
边界外默认值(当 boundary_mode 为 BoundaryBehaviour.CONSTANT 时使用)。
|
||||||
|
default_interpolation_function : FittingFunctionType
|
||||||
|
新关键帧的默认 out_interp。
|
||||||
|
boundary_mode : BoundaryBehaviour
|
||||||
|
范围外行为:
|
||||||
|
- BoundaryBehaviour.CONSTANT: 返回 base_value
|
||||||
|
- BoundaryBehaviour.HOLD: 保持首/尾关键帧值
|
||||||
|
"""
|
||||||
|
self.base_line = base_value
|
||||||
|
self.base_interpolation_function = default_interpolation_function
|
||||||
|
self.boundary_behaviour = boundary_mode
|
||||||
|
|
||||||
|
self._keys: List[Keyframe] = []
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return bool(self._keys) or (self.base_line != 0)
|
||||||
|
|
||||||
|
def add_key(
|
||||||
|
self,
|
||||||
|
time: float,
|
||||||
|
value: float,
|
||||||
|
out_interp: Optional[Callable[[float], float]] = None,
|
||||||
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
use_bezier: bool = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
添加或更新关键帧。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
time : float
|
||||||
|
关键帧时间。
|
||||||
|
value : float
|
||||||
|
参数值。
|
||||||
|
out_interp : Optional[Callable]
|
||||||
|
出插值函数(若 use_bezier=False)。
|
||||||
|
in_tangent : Optional[Tuple[float, float]]
|
||||||
|
入切线偏移 (dt, dv)。dt 通常为负(表示左侧),但存储为绝对偏移。
|
||||||
|
out_tangent : Optional[Tuple[float, float]]
|
||||||
|
出切线偏移 (dt, dv)。dt 通常为正。
|
||||||
|
use_bezier : bool
|
||||||
|
是否使用贝塞尔插值。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
若时间已存在,更新该关键帧的所有属性。
|
||||||
|
"""
|
||||||
|
interp = (
|
||||||
|
out_interp if out_interp is not None else self.base_interpolation_function
|
||||||
|
)
|
||||||
|
new_key = Keyframe(time, value, interp, in_tangent, out_tangent, use_bezier)
|
||||||
|
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
self._keys[idx] = new_key
|
||||||
|
else:
|
||||||
|
self._keys.insert(idx, new_key)
|
||||||
|
|
||||||
|
def remove_key(self, time: float):
|
||||||
|
"""
|
||||||
|
移除指定时间的关键帧。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
time : float
|
||||||
|
要移除的关键帧时间。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
del self._keys[idx]
|
||||||
|
|
||||||
|
def update_key_value(self, time: float, new_value: float):
|
||||||
|
"""更新关键帧值,保留其他属性。"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
k = self._keys[idx]
|
||||||
|
self._keys[idx] = Keyframe(
|
||||||
|
time, new_value, k.out_interp, k.in_tangent, k.out_tangent, k.use_bezier
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_key_interp(
|
||||||
|
self,
|
||||||
|
time: float,
|
||||||
|
out_interp: Optional[Callable[[float], float]] = None,
|
||||||
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
use_bezier: bool = False,
|
||||||
|
):
|
||||||
|
"""更新关键帧的插值属性。"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
k = self._keys[idx]
|
||||||
|
new_value = k.value
|
||||||
|
interp = out_interp if out_interp is not None else k.out_interp
|
||||||
|
self._keys[idx] = Keyframe(
|
||||||
|
time, new_value, interp, in_tangent, out_tangent, use_bezier
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_key_tangents(
|
||||||
|
self,
|
||||||
|
time: float,
|
||||||
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
use_bezier: bool = True,
|
||||||
|
):
|
||||||
|
"""单独设置关键帧的切线,不改变值。"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
k = self._keys[idx]
|
||||||
|
self._keys[idx] = Keyframe(
|
||||||
|
time,
|
||||||
|
k.value,
|
||||||
|
out_interp=k.out_interp,
|
||||||
|
in_tangent=in_tangent,
|
||||||
|
out_tangent=out_tangent,
|
||||||
|
use_bezier=use_bezier,
|
||||||
|
)
|
||||||
|
|
||||||
|
def make_key_smooth(self, time: float):
|
||||||
|
"""
|
||||||
|
将关键帧设为“平滑”模式(自动对称切线,并设为贝塞尔模式)。
|
||||||
|
切线长度基于相邻关键帧的时间和值差。
|
||||||
|
"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
k = self._keys[idx]
|
||||||
|
prev_k = self._keys[idx - 1] if idx > 0 else None
|
||||||
|
next_k = self._keys[idx + 1] if idx + 1 < len(self._keys) else None
|
||||||
|
|
||||||
|
# 默认切线长度:时间差的 1/3,值差按比例
|
||||||
|
dt_in = dt_out = 0.1
|
||||||
|
dv_in = dv_out = 0.0
|
||||||
|
|
||||||
|
if prev_k and next_k:
|
||||||
|
dt_total = next_k.time - prev_k.time
|
||||||
|
dv_total = next_k.value - prev_k.value
|
||||||
|
dt_in = dt_out = dt_total / 3.0
|
||||||
|
dv_in = dv_out = dv_total / 3.0
|
||||||
|
elif prev_k:
|
||||||
|
dt_out = (k.time - prev_k.time) / 2.0
|
||||||
|
dv_out = (k.value - prev_k.value) / 2.0
|
||||||
|
dt_in = dt_out
|
||||||
|
dv_in = dv_out
|
||||||
|
elif next_k:
|
||||||
|
dt_in = (next_k.time - k.time) / 2.0
|
||||||
|
dv_in = (next_k.value - k.value) / 2.0
|
||||||
|
dt_out = dt_in
|
||||||
|
dv_out = dv_in
|
||||||
|
|
||||||
|
self.set_key_tangents(
|
||||||
|
time,
|
||||||
|
in_tangent=(-dt_in, -dv_in), # in_tangent 存储为偏移,使用时做减法
|
||||||
|
out_tangent=(dt_out, dv_out),
|
||||||
|
use_bezier=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_boundary_value(self, t: float) -> float:
|
||||||
|
"""根据 boundary_mode 获取范围外的值。"""
|
||||||
|
if not self._keys:
|
||||||
|
return self.base_line
|
||||||
|
if self.boundary_behaviour == BoundaryBehaviour.CONSTANT:
|
||||||
|
return self.base_line
|
||||||
|
elif self.boundary_behaviour == BoundaryBehaviour.HOLD:
|
||||||
|
if t < self._keys[0].time:
|
||||||
|
return self._keys[0].value
|
||||||
|
else:
|
||||||
|
return self._keys[-1].value
|
||||||
|
else: # 可能会有别的模式吗?
|
||||||
|
return self.base_line
|
||||||
|
|
||||||
|
def value_at(self, t: float) -> float:
|
||||||
|
"""
|
||||||
|
计算时间 t 处的曲线值。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
t : float
|
||||||
|
查询时间。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值结果。
|
||||||
|
"""
|
||||||
|
keys = self._keys
|
||||||
|
if not keys:
|
||||||
|
return self._get_boundary_value(t)
|
||||||
|
|
||||||
|
if t < keys[0].time or t > keys[-1].time:
|
||||||
|
return self._get_boundary_value(t)
|
||||||
|
|
||||||
|
times = [k.time for k in keys]
|
||||||
|
idx = bisect.bisect_right(times, t) - 1
|
||||||
|
|
||||||
|
if idx < 0:
|
||||||
|
return self._get_boundary_value(t)
|
||||||
|
if idx >= len(keys) - 1:
|
||||||
|
return keys[-1].value
|
||||||
|
|
||||||
|
k0 = keys[idx]
|
||||||
|
k1 = keys[idx + 1]
|
||||||
|
|
||||||
|
if k0.time == k1.time:
|
||||||
|
return k0.value
|
||||||
|
if k0.time == t:
|
||||||
|
return k0.value
|
||||||
|
if k1.time == t:
|
||||||
|
return k1.value
|
||||||
|
|
||||||
|
t0, v0 = k0.time, k0.value
|
||||||
|
t1, v1 = k1.time, k1.value
|
||||||
|
u = (t - t0) / (t1 - t0)
|
||||||
|
u = max(0.0, min(1.0, u))
|
||||||
|
|
||||||
|
# 贝塞尔模式(高优先级)
|
||||||
|
if k0.use_bezier or k1.use_bezier:
|
||||||
|
return _evaluate_bezier_segment(
|
||||||
|
t0,
|
||||||
|
v0,
|
||||||
|
t1,
|
||||||
|
v1,
|
||||||
|
out_tangent=k0.out_tangent,
|
||||||
|
in_tangent=k1.in_tangent, # ← 关键:使用下一帧的 in_tangent!
|
||||||
|
u=u,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 函数插值模式,优先处理阶梯保持模式
|
||||||
|
elif k0.out_interp is InterpolationMethod.hold:
|
||||||
|
return v0
|
||||||
|
|
||||||
|
interp_func = k0.out_interp or self.base_interpolation_function
|
||||||
|
v_norm = interp_func(u)
|
||||||
|
return v0 + v_norm * (v1 - v0)
|
||||||
|
|
||||||
|
def __call__(self, t: float) -> float:
|
||||||
|
return self.value_at(t)
|
||||||
|
|
||||||
|
def get_all_keys(self) -> List[Tuple[float, float]]:
|
||||||
|
"""返回 (time, value) 列表。"""
|
||||||
|
return [(k.time, k.value) for k in self._keys]
|
||||||
|
|
||||||
|
def set_default_interpolation_function(self, interp_func: Callable[[float], float]):
|
||||||
|
"""设置默认插值函数。"""
|
||||||
|
self.base_interpolation_function = interp_func
|
||||||
|
|
||||||
|
def set_boundary_mode(
|
||||||
|
self, mode: BoundaryBehaviour, base_value: Optional[float] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
设置边界行为。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
mode : BoundaryBehaviour
|
||||||
|
边界行为设定
|
||||||
|
base_value : Optional[float]
|
||||||
|
当 mode=BoundaryBehaviour.CONSTANT 时,指定新的默认值。
|
||||||
|
"""
|
||||||
|
self.boundary_behaviour = mode
|
||||||
|
if base_value is not None:
|
||||||
|
self.base_line = base_value
|
||||||
|
|
||||||
|
def bake(
|
||||||
|
self,
|
||||||
|
start: float,
|
||||||
|
end: float,
|
||||||
|
sample_rate: Optional[float] = None,
|
||||||
|
num_samples: Optional[int] = None,
|
||||||
|
dtype: Any = None,
|
||||||
|
) -> "np.ndarray": # type: ignore 这里这样用会报错吗?不知道,但是人工智能这样写了都,大抵是能用的吧
|
||||||
|
"""
|
||||||
|
将参数曲线在指定时间范围内烘焙为 NumPy 数组,用于高性能实时查询或音频渲染。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
start : float
|
||||||
|
烘焙起始时间(包含)。
|
||||||
|
end : float
|
||||||
|
烘焙结束时间(不包含)。
|
||||||
|
sample_rate : Optional[float]
|
||||||
|
采样率(单位:样本/时间单位)。例如,若时间单位为秒,sample_rate=48000 表示每秒 48k 样本。
|
||||||
|
必须与 `num_samples` 二选一提供。
|
||||||
|
num_samples : Optional[int]
|
||||||
|
输出数组的总样本数。若提供,则忽略 `sample_rate`。
|
||||||
|
dtype : Any, optional
|
||||||
|
输出数组的数据类型(如 np.float32)。默认为 np.float64。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
np.ndarray
|
||||||
|
一维 NumPy 数组,长度为 `num_samples`,`arr[i] ≈ curve(start + i / sample_rate)`。
|
||||||
|
|
||||||
|
Exceptions
|
||||||
|
----------
|
||||||
|
ValueError
|
||||||
|
- 若 `start >= end`
|
||||||
|
- 若未提供 `sample_rate` 且未提供 `num_samples`
|
||||||
|
- 若 `num_samples <= 0`
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
- 内部使用 `np.linspace` 生成时间轴,然后逐点调用 `self.value_at(t)`。
|
||||||
|
- 虽然目前是 Python 循环,但对于典型自动化曲线(<1000 关键帧),NumPy 向量化优势主要体现在内存布局和后续处理。
|
||||||
|
- 如需极致性能(如 >1M 样本),可未来优化为 C++/Numba 加速,但当前已满足 DAW 自动化需求。
|
||||||
|
"""
|
||||||
|
if start >= end:
|
||||||
|
raise ValueError("起始值须小于结束值。")
|
||||||
|
|
||||||
|
if num_samples is not None:
|
||||||
|
if num_samples <= 0:
|
||||||
|
raise ValueError("烘焙的采样数须为非零自然数。")
|
||||||
|
n = num_samples
|
||||||
|
elif sample_rate is not None:
|
||||||
|
if sample_rate <= 0:
|
||||||
|
raise ValueError("烘焙的采样率须为正值。")
|
||||||
|
duration = end - start
|
||||||
|
n = int(ceil(duration * sample_rate))
|
||||||
|
# 别因为小数数值会产生的问题而越界了来着
|
||||||
|
if n == 0:
|
||||||
|
n = 1
|
||||||
|
else:
|
||||||
|
raise ValueError("烘焙参数时,须提供采样率或采样数。")
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# 生成对应时间的节点:[start, ..., end - dt]
|
||||||
|
times = np.linspace(start, end, n, endpoint=False)
|
||||||
|
|
||||||
|
# 计算每个时间节点上的参数值
|
||||||
|
# 我们认为在数字音频工作站的环境里,此值可能最多到 ~1e6 的样子,因此这样 for 一下应当可以接受
|
||||||
|
# WARNING: 人工智能是这样理解的,如果有问题的话后续可能需要更改
|
||||||
|
values = np.empty(n, dtype=dtype or np.float64)
|
||||||
|
for i in range(n):
|
||||||
|
values[i] = self.value_at(float(times[i]))
|
||||||
|
|
||||||
|
return values
|
||||||
440
Musicreater/plugins.py
Normal file
440
Musicreater/plugins.py
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
# -*- 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
|
||||||
|
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import (
|
||||||
|
Dict,
|
||||||
|
Any,
|
||||||
|
Optional,
|
||||||
|
List,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
Generator,
|
||||||
|
Set,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
TypeVar,
|
||||||
|
Mapping,
|
||||||
|
Callable,
|
||||||
|
Type,
|
||||||
|
)
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
|
||||||
|
from ._plugin_abc import (
|
||||||
|
# 枚举类
|
||||||
|
PluginTypes,
|
||||||
|
# 抽象基类/数据类(插件参数定义)
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
# 抽象基类(插件定义)
|
||||||
|
MusicInputPluginBase,
|
||||||
|
TrackInputPluginBase,
|
||||||
|
MusicOperatePluginBase,
|
||||||
|
TrackOperatePluginBase,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
ServicePluginBase,
|
||||||
|
LibraryPluginBase,
|
||||||
|
# 顶层插件定义
|
||||||
|
TopPluginBase,
|
||||||
|
)
|
||||||
|
from .exceptions import (
|
||||||
|
PluginMetainfoNotFoundError,
|
||||||
|
ParameterTypeError,
|
||||||
|
PluginInstanceNotFoundError,
|
||||||
|
PluginRegisteredError,
|
||||||
|
PluginNotFoundError,
|
||||||
|
PluginDependencyNotFound,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# 枚举类
|
||||||
|
"PluginTypes",
|
||||||
|
# 抽象基类/数据类(插件参数定义)
|
||||||
|
"PluginConfig",
|
||||||
|
"PluginMetaInformation",
|
||||||
|
# 抽象基类(插件定义)
|
||||||
|
"MusicInputPluginBase",
|
||||||
|
"TrackInputPluginBase",
|
||||||
|
"MusicOperatePluginBase",
|
||||||
|
"TrackOperatePluginBase",
|
||||||
|
"MusicOutputPluginBase",
|
||||||
|
"TrackOutputPluginBase",
|
||||||
|
"ServicePluginBase",
|
||||||
|
"LibraryPluginBase",
|
||||||
|
# 插件注册用装饰函数
|
||||||
|
"music_input_plugin",
|
||||||
|
"track_input_plugin",
|
||||||
|
"music_operate_plugin",
|
||||||
|
"track_operate_plugin",
|
||||||
|
"music_output_plugin",
|
||||||
|
"track_output_plugin",
|
||||||
|
"service_plugin",
|
||||||
|
"library_plugin",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
T_IOPlugin = TypeVar(
|
||||||
|
"T_IOPlugin",
|
||||||
|
MusicInputPluginBase,
|
||||||
|
TrackInputPluginBase,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
)
|
||||||
|
T_Plugin = TypeVar(
|
||||||
|
"T_Plugin",
|
||||||
|
MusicInputPluginBase,
|
||||||
|
TrackInputPluginBase,
|
||||||
|
MusicOperatePluginBase,
|
||||||
|
TrackOperatePluginBase,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
ServicePluginBase,
|
||||||
|
LibraryPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_plugin_module(package: Union[Path, str]):
|
||||||
|
"""自动发现并加载插件包中的插件
|
||||||
|
|
||||||
|
参数:
|
||||||
|
=====
|
||||||
|
package: Path | str, 可选
|
||||||
|
插件包路径或名称,当为 Path 类时为路径,为 str 时为包名,切勿混淆。
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
if isinstance(package, Path):
|
||||||
|
relative_path = package.resolve().relative_to(Path.cwd().resolve())
|
||||||
|
if relative_path.stem == "__init__":
|
||||||
|
return importlib.import_module(".".join(relative_path.parts[:-1]))
|
||||||
|
else:
|
||||||
|
return importlib.import_module(
|
||||||
|
".".join(relative_path.parts[:-1] + (relative_path.stem,))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return importlib.import_module(package)
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise PluginNotFoundError("无法找到名为`{}`的插件包".format(package)) from e
|
||||||
|
|
||||||
|
|
||||||
|
class PluginRegistry:
|
||||||
|
"""插件注册管理器(注册表)"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._music_input_plugins: Dict[str, MusicInputPluginBase] = {}
|
||||||
|
self._track_input_plugins: Dict[str, TrackInputPluginBase] = {}
|
||||||
|
self._music_operate_plugins: Dict[str, MusicOperatePluginBase] = {}
|
||||||
|
self._track_operate_plugins: Dict[str, TrackOperatePluginBase] = {}
|
||||||
|
self._music_output_plugins: Dict[str, MusicOutputPluginBase] = {}
|
||||||
|
self._track_output_plugins: Dict[str, TrackOutputPluginBase] = {}
|
||||||
|
self._service_plugins: Dict[str, ServicePluginBase] = {}
|
||||||
|
self._library_plugins: Dict[str, LibraryPluginBase] = {}
|
||||||
|
self._all_plugin_id: List = []
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[Tuple[PluginTypes, Mapping[str, TopPluginBase]]]:
|
||||||
|
"""迭代器,返回所有插件"""
|
||||||
|
return iter(
|
||||||
|
(
|
||||||
|
(PluginTypes.FUNCTION_MUSIC_IMPORT, self._music_input_plugins),
|
||||||
|
(PluginTypes.FUNCTION_TRACK_IMPORT, self._track_input_plugins),
|
||||||
|
(PluginTypes.FUNCTION_MUSIC_OPERATE, self._music_operate_plugins),
|
||||||
|
(PluginTypes.FUNCTION_TRACK_OPERATE, self._track_operate_plugins),
|
||||||
|
(PluginTypes.FUNCTION_MUSIC_EXPORT, self._music_output_plugins),
|
||||||
|
(PluginTypes.FUNCTION_TRACK_EXPORT, self._track_output_plugins),
|
||||||
|
(PluginTypes.SERVICE, self._service_plugins),
|
||||||
|
(PluginTypes.LIBRARY, self._library_plugins),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _register_plugin(
|
||||||
|
self, cls_dict: dict, plg_class: Type[TopPluginBase], plg_id: str
|
||||||
|
) -> None:
|
||||||
|
"""注册插件"""
|
||||||
|
if plg_id in cls_dict:
|
||||||
|
if cls_dict[plg_id].metainfo.version >= plg_class.metainfo.version:
|
||||||
|
raise PluginRegisteredError(
|
||||||
|
"插件惟一识别码`{}`所对应的插件已存在更高版本`{}`,请勿重复注册同一插件。".format(
|
||||||
|
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()
|
||||||
|
self._all_plugin_id.append(plg_id)
|
||||||
|
|
||||||
|
def register_music_input_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册输入插件-整首曲目"""
|
||||||
|
self._register_plugin(self._music_input_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_track_input_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册输入插件-单个音轨"""
|
||||||
|
self._register_plugin(self._track_input_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_music_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册曲目处理插件"""
|
||||||
|
self._register_plugin(self._music_operate_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_track_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册音轨处理插件"""
|
||||||
|
self._register_plugin(self._track_operate_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_music_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册输出插件-整首曲目"""
|
||||||
|
self._register_plugin(self._music_output_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_track_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册输出插件-单个音轨"""
|
||||||
|
self._register_plugin(self._track_output_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_service_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册服务插件"""
|
||||||
|
self._register_plugin(self._service_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_library_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册支持库插件"""
|
||||||
|
self._register_plugin(self._library_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_io_plugin_by_format(
|
||||||
|
plugin_regdict: Mapping[str, T_IOPlugin], fpath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[T_IOPlugin, None, None]:
|
||||||
|
if isinstance(fpath_or_format, str):
|
||||||
|
return (
|
||||||
|
plugin
|
||||||
|
for plugin in plugin_regdict.values()
|
||||||
|
if plugin.can_handle_format(fpath_or_format)
|
||||||
|
)
|
||||||
|
elif isinstance(fpath_or_format, Path):
|
||||||
|
# print("在",plugin_regdict,"中,查找可用于处理",fpath_or_format,"的插件")
|
||||||
|
return (
|
||||||
|
plugin
|
||||||
|
for plugin in plugin_regdict.values()
|
||||||
|
if plugin.can_handle_file(fpath_or_format)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ParameterTypeError(
|
||||||
|
"用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||||
|
type(fpath_or_format), fpath_or_format
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_music_input_plugin_by_format(
|
||||||
|
self, filepath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[MusicInputPluginBase, None, None]:
|
||||||
|
"""通过指定输入的文件或格式,以获取对应的全曲导入用插件"""
|
||||||
|
return self._get_io_plugin_by_format(
|
||||||
|
self._music_input_plugins, filepath_or_format
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_input_plugin_by_format(
|
||||||
|
self, filepath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[TrackInputPluginBase, None, None]:
|
||||||
|
"""通过指定输入的文件或格式,以获取对应的单音轨导入用插件"""
|
||||||
|
return self._get_io_plugin_by_format(
|
||||||
|
self._track_input_plugins, filepath_or_format
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_music_output_plugin_by_format(
|
||||||
|
self, filepath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[MusicOutputPluginBase, None, None]:
|
||||||
|
"""通过指定输出的文件或格式,以获取对应的导出全曲用插件"""
|
||||||
|
return self._get_io_plugin_by_format(
|
||||||
|
self._music_output_plugins, filepath_or_format
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_output_plugin_by_format(
|
||||||
|
self, filepath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[TrackOutputPluginBase, None, None]:
|
||||||
|
"""通过指定输出的文件或格式,以获取对应的导出单个音轨用插件"""
|
||||||
|
return self._get_io_plugin_by_format(
|
||||||
|
self._track_output_plugins, filepath_or_format
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_plugin_by_name(
|
||||||
|
self,
|
||||||
|
plugin_regdict: Mapping[str, T_Plugin],
|
||||||
|
plugin_name: str,
|
||||||
|
plugin_usage: str = "",
|
||||||
|
) -> T_Plugin:
|
||||||
|
"""通过指定名称,以获取对应的插件,当名称重叠时,取版本号最大的"""
|
||||||
|
try:
|
||||||
|
return max(
|
||||||
|
[
|
||||||
|
plugin
|
||||||
|
for plugin in plugin_regdict.values()
|
||||||
|
if plugin.metainfo.name == plugin_name
|
||||||
|
],
|
||||||
|
key=lambda plugin: plugin.metainfo.version,
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
raise PluginInstanceNotFoundError(
|
||||||
|
"未找到“用于{}、名为`{}`”的插件".format(plugin_usage, plugin_name)
|
||||||
|
) from e
|
||||||
|
|
||||||
|
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPluginBase:
|
||||||
|
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._music_input_plugins, plugin_name, "导入全曲"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_input_plugin(self, plugin_name: str) -> TrackInputPluginBase:
|
||||||
|
"""获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._track_input_plugins, plugin_name, "导入单轨"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePluginBase:
|
||||||
|
"""获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._music_operate_plugins, plugin_name, "处理整个曲目"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePluginBase:
|
||||||
|
"""获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._track_operate_plugins, plugin_name, "处理单个音轨"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPluginBase:
|
||||||
|
"""获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._music_output_plugins, plugin_name, "导出完整曲目"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPluginBase:
|
||||||
|
"""获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._track_output_plugins, plugin_name, "导出单个音轨"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_service_plugin(self, plugin_name: str) -> ServicePluginBase:
|
||||||
|
"""获取服务用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(self._service_plugins, plugin_name, "提供服务")
|
||||||
|
|
||||||
|
def get_library_plugin(self, plugin_name: str) -> LibraryPluginBase:
|
||||||
|
"""获取依赖库类插件,当名称重叠时,取版本号最高的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._library_plugins, plugin_name, "作为依赖库"
|
||||||
|
)
|
||||||
|
|
||||||
|
def supported_input_formats(self) -> Set[str]:
|
||||||
|
"""所有支持的导入格式"""
|
||||||
|
return set(
|
||||||
|
chain.from_iterable(
|
||||||
|
plugin.supported_formats
|
||||||
|
for plugin in chain(
|
||||||
|
self._music_input_plugins.values(),
|
||||||
|
self._track_input_plugins.values(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def supported_output_formats(self) -> Set[str]:
|
||||||
|
"""所有支持的导出格式"""
|
||||||
|
return set(
|
||||||
|
chain.from_iterable(
|
||||||
|
plugin.supported_formats
|
||||||
|
for plugin in chain(
|
||||||
|
self._music_output_plugins.values(),
|
||||||
|
self._track_output_plugins.values(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_global_plugin_registry = PluginRegistry()
|
||||||
|
"""全局插件注册表实例"""
|
||||||
|
|
||||||
|
|
||||||
|
def __plugin_regist_decorator(plg_id: str, rgst_func: Callable[[type, str], None]):
|
||||||
|
|
||||||
|
def decorator(cls):
|
||||||
|
global _global_plugin_registry
|
||||||
|
cls.id = plg_id
|
||||||
|
rgst_func(cls, plg_id)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def music_input_plugin(plugin_id: str):
|
||||||
|
"""全曲输入用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_music_input_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def track_input_plugin(plugin_id: str):
|
||||||
|
"""单轨输入用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_track_input_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def music_operate_plugin(plugin_id: str):
|
||||||
|
"""全曲处理用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_music_operate_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def track_operate_plugin(plugin_id: str):
|
||||||
|
"""音轨处理插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_track_operate_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def music_output_plugin(plugin_id: str):
|
||||||
|
"""乐曲输出用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_music_output_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def track_output_plugin(plugin_id: str):
|
||||||
|
"""音轨输出用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_track_output_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def service_plugin(plugin_id: str):
|
||||||
|
"""服务插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_service_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def library_plugin(plugin_id: str):
|
||||||
|
"""支持库插件装饰器"""
|
||||||
|
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_library_plugin
|
||||||
|
)
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存放数据类型的定义
|
音·创 v3 定义的一些数据类型,可以用于类型检查器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
版权所有 © 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
|
||||||
@@ -16,58 +16,6 @@ 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 Callable, Dict, List, Literal, Mapping, Tuple, Union
|
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
|
||||||
|
|
||||||
from .subclass import MineNote
|
|
||||||
|
|
||||||
MidiNoteNameTableType = Mapping[int, Tuple[str, ...]]
|
|
||||||
"""
|
|
||||||
Midi音符名称对照表类型
|
|
||||||
"""
|
|
||||||
|
|
||||||
MidiInstrumentTableType = Mapping[int, str]
|
|
||||||
"""
|
|
||||||
Midi乐器对照表类型
|
|
||||||
"""
|
|
||||||
|
|
||||||
FittingFunctionType = Callable[[float], float]
|
|
||||||
"""
|
|
||||||
拟合函数类型
|
|
||||||
"""
|
|
||||||
|
|
||||||
ChannelType = Dict[
|
|
||||||
int,
|
|
||||||
Dict[
|
|
||||||
int,
|
|
||||||
List[
|
|
||||||
Union[
|
|
||||||
Tuple[Literal["PgmC"], int, int],
|
|
||||||
Tuple[Literal["NoteS"], int, int, int],
|
|
||||||
Tuple[Literal["NoteE"], int, int],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
以字典所标记的通道信息类型(已弃用)
|
|
||||||
|
|
||||||
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
MineNoteChannelType = Mapping[
|
|
||||||
int,
|
|
||||||
List[MineNote,],
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
我的世界通道信息类型
|
|
||||||
|
|
||||||
Dict[int,Dict[int,List[MineNote,],],]
|
|
||||||
"""
|
|
||||||
|
|
||||||
MineNoteTrackType = Mapping[
|
|
||||||
int,
|
|
||||||
List[MineNote,],
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
24
README.md
24
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: 诸葛亮与八卦阵]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
|
[Bilibili: 玉衡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: 诸葛亮与八卦阵]](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)
|
||||||
@@ -63,7 +66,7 @@
|
|||||||
pip install --upgrade -i https://pypi.python.org/simple Musicreater
|
pip install --upgrade -i https://pypi.python.org/simple Musicreater
|
||||||
```
|
```
|
||||||
|
|
||||||
- 克隆仓库并安装(最新版本但**不推荐**)
|
- 克隆仓库并安装(最新内容但**不推荐**)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://gitee.com/TriM-Organization/Musicreater.git
|
git clone https://gitee.com/TriM-Organization/Musicreater.git
|
||||||
@@ -83,7 +86,7 @@
|
|||||||
|
|
||||||
**金羿 Eilles**:我的世界基岩版指令作者,个人开发者,B 站不知名 UP 主。
|
**金羿 Eilles**:我的世界基岩版指令作者,个人开发者,B 站不知名 UP 主。
|
||||||
|
|
||||||
**诸葛亮与八卦阵 bgArray**:我的世界基岩版玩家,喜欢编程和音乐,深圳学生。
|
**玉衡Alioth Alioth**:我的世界基岩版玩家,喜欢编程和音乐,学生。
|
||||||
|
|
||||||
**偷吃不是Touch Touch**:我的世界基岩版指令制作者,提供测试支持
|
**偷吃不是Touch Touch**:我的世界基岩版指令制作者,提供测试支持
|
||||||
|
|
||||||
@@ -97,7 +100,7 @@
|
|||||||
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法,我们将其改编并应用;同时,感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力,让我们在原本一骑绝尘的摸鱼道路上转向开发。
|
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法,我们将其改编并应用;同时,感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力,让我们在原本一骑绝尘的摸鱼道路上转向开发。
|
||||||
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题,辅助我们找到了视窗操作系统下的兼容性问题;感谢其反馈延迟播放器出现的重大问题,让我们得以修改全部延迟播放错误;尤其感谢他对于我们的软件的大力宣传
|
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题,辅助我们找到了视窗操作系统下的兼容性问题;感谢其反馈延迟播放器出现的重大问题,让我们得以修改全部延迟播放错误;尤其感谢他对于我们的软件的大力宣传
|
||||||
- 感谢 **Ammelia “艾米利亚”**\<QQ2838334637\> 敦促我们进行新的功能开发,并为新功能提出了非常优秀的大量建议,以及提供的 BDX 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
|
- 感谢 **Ammelia “艾米利亚”**\<QQ2838334637\> 敦促我们进行新的功能开发,并为新功能提出了非常优秀的大量建议,以及提供的 BDX 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
|
||||||
- 感谢 **[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”** 对我们项目的支持与宣传,非常感谢他为我们提供的服务器!
|
- 感谢 **[神羽 “SnowyKami”](https://www.sfkm.me/)** 对我们项目的支持与宣传,非常感谢他为我们提供的服务器!
|
||||||
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误,并提醒我们修复一个一直存在的大 bug。
|
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误,并提醒我们修复一个一直存在的大 bug。
|
||||||
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误,并提醒修复 bug。
|
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误,并提醒修复 bug。
|
||||||
- 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题。
|
- 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题。
|
||||||
@@ -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
README_EN.md
14
README_EN.md
@@ -1,5 +1,5 @@
|
|||||||
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
|
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFEilles-00A1E7?style=for-the-badge
|
||||||
[Bilibili: bgArray]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
|
[Bilibili: Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-00A1E7?style=for-the-badge
|
||||||
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
|
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
|
||||||
[python]: https://img.shields.io/badge/python-3.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/EillesWan/Musicreater?style=for-the-badge
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</img>
|
</img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 align="center">A free open-source library of <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">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<p>
|
<p>
|
||||||
|
|
||||||
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
|
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
|
||||||
[![][Bilibili: bgArray]](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)
|
||||||
@@ -35,11 +35,11 @@
|
|||||||
|
|
||||||
[简体中文 🇨🇳](README.md) | English🇬🇧
|
[简体中文 🇨🇳](README.md) | English🇬🇧
|
||||||
|
|
||||||
**Notice that the localizations of documents may NOT be up-to-date.**
|
**Notice that the localizations of documents may probably NOT be up-to-date. The original document is in Chinese.**
|
||||||
|
|
||||||
## Introduction🚀
|
## Introduction🚀
|
||||||
|
|
||||||
Musicreater is a free open-source library used for digital music that being played in _Minecraft_.
|
Musicreater is a free open-source library used for handling with digital musics that being able to be played in _Minecraft_.
|
||||||
|
|
||||||
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ Commands such as `python`、`pip` could be changed to some like `python3` or `pi
|
|||||||
|
|
||||||
**Eilles (金羿)**:A student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
|
**Eilles (金羿)**:A student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
|
||||||
|
|
||||||
**bgArray (诸葛亮与八卦阵)**: A student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
|
**Alioth (玉衡Alioth)**: A student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
|
||||||
|
|
||||||
**Touch (偷吃不是 Touch)**: A man who is good at using command(s) in _Minecraft: Bedrock Edition_, who supported us of debugging and testing program and algorithm
|
**Touch (偷吃不是 Touch)**: A man who is good at using command(s) in _Minecraft: Bedrock Edition_, who supported us of debugging and testing program and algorithm
|
||||||
|
|
||||||
|
|||||||
53
TO-DO.md
Normal file
53
TO-DO.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# 任务清单
|
||||||
|
|
||||||
|
## 待办事项
|
||||||
|
- 乐曲文件格式设计
|
||||||
|
目前想到的是:
|
||||||
|
1. 使用 `.MCT` 作为项目文件的后缀,然后考虑一下格式是否和之前的 MusicSequence 兼容,如果兼容的话可以照旧用 `.MSQ`,如不的话,可以试试想一个新的后缀名作为数据文件后缀
|
||||||
|
2. 要求数据文件支持完全流式读入
|
||||||
|
|
||||||
|
- [] 音轨静音处理
|
||||||
|
当前没有处理
|
||||||
|
|
||||||
|
- [] 优化音轨的存储方式
|
||||||
|
当前是用列表,且每一次变动元素都要重新排序,这样消耗太大了,需要优化,改用最小堆形式(heapq)
|
||||||
|
|
||||||
|
- 移植 v2 功能到内置插件
|
||||||
|
目前 v2 的功能有很多,都要移植到 v3。
|
||||||
|
1. [x] 导入 Midi 文件到全曲
|
||||||
|
2. [] 导入 Midi 文件到指定轨道
|
||||||
|
3. [x] 导出到延迟播放器的结构文件(MCSTRUCTURE、BDX)
|
||||||
|
4. [] 导出到延迟播放器的附加包
|
||||||
|
5. [] 导出到积分板播放器的以上两种形式
|
||||||
|
6. [] 导出到中继器播放器的以上两种形式
|
||||||
|
7. [] 在 WebSocket 播放器中播放
|
||||||
|
8. [] 导出到支持神羽资源包的以上 7 种形式
|
||||||
|
9. [] 对于 Midi 歌词的实验性功能
|
||||||
|
10. [] 对于 Java 版本适配的实验性功能
|
||||||
|
11. [] 对于听感优化的实验性功能(插值、偏移)
|
||||||
|
|
||||||
|
- [] 测试参数曲线的功能
|
||||||
|
|
||||||
|
- [] 支持导出音符盒构成的音乐
|
||||||
|
|
||||||
|
- [] 支持导出成 schematic 结构
|
||||||
|
|
||||||
|
## 讨论
|
||||||
|
|
||||||
|
1. [x] 是否应该在插件注册表 PluginRegistry 中采用 `Dict[插件名, 插件对象]` 的形式存储插件?
|
||||||
|
当前不采用这种方式是认为可以兼容一些极端场景下用户将一堆同名插件放在一起的情况。但是就算是插件放在一起,我们也可以有选择地读入注册表,比如依照版本号只读取最高版本的插件,并不需要全部存储在插件注册表中。所以其实用字典来存储是有利的?吗?
|
||||||
|
|
||||||
|
**当前已解决**
|
||||||
|
|
||||||
|
引入了插件惟一识别码之后当然是采用 `Dict[插件唯一识别码, 插件对象]` 来存储插件了~之前插件名称的内容是我想得太浅了,我写完所有代码之后才想到插件名称是中文还带空格的任意字符串……
|
||||||
|
|
||||||
|
2. [] 服务插件到底该怎么写?总不能留着一个 PluginType.SERVICE 的插件一直空在那里吧……
|
||||||
|
|
||||||
|
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 方法,会直接用上面展示过的方法读取数据
|
||||||
@@ -18,8 +18,8 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
from .exceptions import *
|
from .old_exceptions import *
|
||||||
from .main import (
|
from .old_main import (
|
||||||
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
|
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
|
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
|
||||||
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
@@ -28,10 +28,10 @@ from .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 .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 (
|
||||||
76
old-things/Musicreater/old_exceptions.py
Normal file
76
old-things/Musicreater/old_exceptions.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
存放一些报错类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||||
|
Copyright © 2025 Eilles & bgArray
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
class MSCTBaseException(Exception):
|
||||||
|
"""音·创 的所有错误均继承于此"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音·创 的所有错误均继承于此"""
|
||||||
|
super().__init__("音·创", *args)
|
||||||
|
|
||||||
|
def meow(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
for i in self.args:
|
||||||
|
print(i + "喵!")
|
||||||
|
|
||||||
|
def crash_it(self):
|
||||||
|
raise self
|
||||||
|
|
||||||
|
|
||||||
|
class MidiDestroyedError(MSCTBaseException):
|
||||||
|
"""Midi文件损坏"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""Midi文件损坏"""
|
||||||
|
super().__init__("MIDI文件损坏:无法读取 MIDI 文件", *args)
|
||||||
|
|
||||||
|
|
||||||
|
# class MidiUnboundError(MSCTBaseException):
|
||||||
|
# """未定义Midi对象(无用)"""
|
||||||
|
|
||||||
|
# def __init__(self, *args):
|
||||||
|
# """未绑定Midi对象"""
|
||||||
|
# super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args)
|
||||||
|
# 此错误在本版本内已经不再使用
|
||||||
|
|
||||||
|
|
||||||
|
class CommandFormatError(MSCTBaseException, RuntimeError):
|
||||||
|
"""指令格式与目标格式不匹配而引起的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""指令格式与目标格式不匹配而引起的错误"""
|
||||||
|
super().__init__("指令格式不匹配", *args)
|
||||||
|
|
||||||
|
|
||||||
|
# class CrossNoteError(MidiFormatException):
|
||||||
|
# """同通道下同音符交叉出现所产生的错误"""
|
||||||
|
|
||||||
|
# def __init__(self, *args):
|
||||||
|
# """同通道下同音符交叉出现所产生的错误"""
|
||||||
|
# super().__init__("同通道下同音符交叉", *args)
|
||||||
|
# 这TM是什么错误?
|
||||||
|
# 我什么时候写的这玩意?
|
||||||
|
# 我哪知道这说的是啥?
|
||||||
|
# !!!
|
||||||
|
# 我知道这是什么了 —— 金羿 2025 0401
|
||||||
|
# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息
|
||||||
|
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
||||||
|
|
||||||
|
|
||||||
148
old-things/Musicreater/old_init.py
Normal file
148
old-things/Musicreater/old_init.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""一个简单的我的世界音频转换库
|
||||||
|
音·创 (Musicreater)
|
||||||
|
是一款免费开源的《我的世界》数字音频支持库。
|
||||||
|
Musicreater(音·创)
|
||||||
|
A free open source library used for dealing with **Minecraft** digital musics.
|
||||||
|
|
||||||
|
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||||
|
Copyright © 2025 Eilles & bgArray
|
||||||
|
|
||||||
|
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
|
||||||
|
The Licensor of Musicreater("this project") is Eilles, bgArray.
|
||||||
|
|
||||||
|
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||||
|
任何人皆可从以下地址获得本协议副本:https://gitee.com/EillesWan/YulvLicenses。
|
||||||
|
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||||
|
详细的准许和限制条款请见原协议文本。
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = "2.4.2.3"
|
||||||
|
__vername__ = "音符附加信息升级"
|
||||||
|
__author__ = (
|
||||||
|
("金羿", "Eilles"),
|
||||||
|
("诸葛亮与八卦阵", "bgArray"),
|
||||||
|
("鱼旧梦", "ElapsingDreams"),
|
||||||
|
("偷吃不是Touch", "Touch"),
|
||||||
|
)
|
||||||
|
__all__ = [
|
||||||
|
# 主要类
|
||||||
|
"MusicSequence",
|
||||||
|
"MidiConvert",
|
||||||
|
# 附加类
|
||||||
|
# "SingleNote",
|
||||||
|
"MineNote",
|
||||||
|
"MineCommand",
|
||||||
|
"SingleNoteBox",
|
||||||
|
"ProgressBarStyle",
|
||||||
|
# "TimeStamp", 未来功能
|
||||||
|
# 字典键
|
||||||
|
"MIDI_PROGRAM",
|
||||||
|
"MIDI_VOLUME",
|
||||||
|
"MIDI_PAN",
|
||||||
|
# 默认值
|
||||||
|
"MIDI_DEFAULT_PROGRAM_VALUE",
|
||||||
|
"MIDI_DEFAULT_VOLUME_VALUE",
|
||||||
|
"DEFAULT_PROGRESSBAR_STYLE",
|
||||||
|
# Midi 自己的对照表
|
||||||
|
"MIDI_PITCH_NAME_TABLE",
|
||||||
|
"MIDI_PITCHED_NOTE_NAME_GROUP",
|
||||||
|
"MIDI_PITCHED_NOTE_NAME_TABLE",
|
||||||
|
"MIDI_PERCUSSION_NOTE_NAME_TABLE",
|
||||||
|
# Minecraft 自己的对照表
|
||||||
|
"MC_PERCUSSION_INSTRUMENT_LIST",
|
||||||
|
"MC_PITCHED_INSTRUMENT_LIST",
|
||||||
|
"MC_INSTRUMENT_BLOCKS_TABLE",
|
||||||
|
"MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE",
|
||||||
|
"MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE",
|
||||||
|
# Midi 与 游戏 的对照表
|
||||||
|
"MM_INSTRUMENT_RANGE_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",
|
||||||
|
# 操作性函数
|
||||||
|
"velocity_2_distance_natural",
|
||||||
|
"velocity_2_distance_straight",
|
||||||
|
"panning_2_rotation_linear",
|
||||||
|
"panning_2_rotation_trigonometric",
|
||||||
|
# 工具函数
|
||||||
|
"load_decode_musicsequence_metainfo",
|
||||||
|
"load_decode_msq_flush_release",
|
||||||
|
"load_decode_fsq_flush_release",
|
||||||
|
"guess_deviation",
|
||||||
|
"mctick2timestr",
|
||||||
|
"midi_inst_to_mc_sound",
|
||||||
|
]
|
||||||
|
|
||||||
|
from .old_main import (
|
||||||
|
MusicSequence,
|
||||||
|
MidiConvert,
|
||||||
|
# 字典键
|
||||||
|
MIDI_PROGRAM,
|
||||||
|
MIDI_PAN,
|
||||||
|
MIDI_VOLUME,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .subclass import (
|
||||||
|
MineNote,
|
||||||
|
MineCommand,
|
||||||
|
SingleNoteBox,
|
||||||
|
ProgressBarStyle,
|
||||||
|
mctick2timestr,
|
||||||
|
DEFAULT_PROGRESSBAR_STYLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .old_utils import (
|
||||||
|
# 兼容性函数
|
||||||
|
load_decode_musicsequence_metainfo,
|
||||||
|
load_decode_msq_flush_release,
|
||||||
|
load_decode_fsq_flush_release,
|
||||||
|
# 工具函数
|
||||||
|
guess_deviation,
|
||||||
|
midi_inst_to_mc_sound,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.midi_read import (
|
||||||
|
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_PITCH_NAME_TABLE,
|
||||||
|
MIDI_PITCHED_NOTE_NAME_GROUP,
|
||||||
|
MIDI_PITCHED_NOTE_NAME_TABLE,
|
||||||
|
MIDI_PERCUSSION_NOTE_NAME_TABLE,
|
||||||
|
# 我的世界 表
|
||||||
|
MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE,
|
||||||
|
MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
|
||||||
|
MC_INSTRUMENT_BLOCKS_TABLE,
|
||||||
|
MC_PERCUSSION_INSTRUMENT_LIST,
|
||||||
|
MC_PITCHED_INSTRUMENT_LIST,
|
||||||
|
# MIDI 到 我的世界 表
|
||||||
|
MM_INSTRUMENT_RANGE_TABLE,
|
||||||
|
MM_INSTRUMENT_DEVIATION_TABLE,
|
||||||
|
)
|
||||||
1870
old-things/Musicreater/old_main.py
Normal file
1870
old-things/Musicreater/old_main.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
from typing import Literal, Optional, Tuple
|
from typing import Literal, Optional, Tuple
|
||||||
|
|
||||||
from ...main import MidiConvert
|
from ...old_main import MidiConvert
|
||||||
from ...subclass import ProgressBarStyle
|
from ...subclass import ProgressBarStyle
|
||||||
from ..archive import behavior_mcpack_manifest, compress_zipfile
|
from ..archive import behavior_mcpack_manifest, compress_zipfile
|
||||||
from ..mcstructure import (
|
from ..mcstructure import (
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
@@ -17,7 +17,7 @@ from typing import Optional
|
|||||||
|
|
||||||
import brotli
|
import brotli
|
||||||
|
|
||||||
from ...main import MidiConvert
|
from ...old_main import MidiConvert
|
||||||
from ...subclass import MineCommand, ProgressBarStyle
|
from ...subclass import MineCommand, ProgressBarStyle
|
||||||
from ..bdx import (
|
from ..bdx import (
|
||||||
bdx_move,
|
bdx_move,
|
||||||
@@ -14,9 +14,9 @@ Terms & Conditions: License.md in the root directory
|
|||||||
import os
|
import os
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from ...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,
|
||||||
@@ -16,8 +16,8 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
from ..exceptions import NotDefineProgramError, ZeroSpeedError
|
from ..old_exceptions import NotDefineProgramError, ZeroSpeedError
|
||||||
from ..main import MidiConvert
|
from ..old_main import MidiConvert
|
||||||
from ..subclass import MineCommand
|
from ..subclass import MineCommand
|
||||||
from ..utils import inst_to_sould_with_deviation, perc_inst_to_soundID_withX
|
from ..utils import inst_to_sould_with_deviation, perc_inst_to_soundID_withX
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ from typing import List, Literal, Optional, Tuple
|
|||||||
|
|
||||||
import fcwslib
|
import fcwslib
|
||||||
|
|
||||||
from ...main import MidiConvert
|
from ...old_main import MidiConvert
|
||||||
from ...subclass import MineCommand, ProgressBarStyle
|
from ...subclass import MineCommand, ProgressBarStyle
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
73
old-things/Musicreater/old_types.py
Normal file
73
old-things/Musicreater/old_types.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
存放数据类型的定义
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||||
|
Copyright © 2025 Eilles & bgArray
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
|
||||||
|
|
||||||
|
from .subclass import MineNote
|
||||||
|
|
||||||
|
MidiNoteNameTableType = Mapping[int, Tuple[str, ...]]
|
||||||
|
"""
|
||||||
|
Midi音符名称对照表类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
MidiInstrumentTableType = Mapping[int, str]
|
||||||
|
"""
|
||||||
|
Midi乐器对照表类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
FittingFunctionType = Callable[[float], float]
|
||||||
|
"""
|
||||||
|
拟合函数类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
ChannelType = Dict[
|
||||||
|
int,
|
||||||
|
Dict[
|
||||||
|
int,
|
||||||
|
List[
|
||||||
|
Union[
|
||||||
|
Tuple[Literal["PgmC"], int, int],
|
||||||
|
Tuple[Literal["NoteS"], int, int, int],
|
||||||
|
Tuple[Literal["NoteE"], int, int],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
以字典所标记的通道信息类型(已弃用)
|
||||||
|
|
||||||
|
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
MineNoteChannelType = Mapping[
|
||||||
|
int,
|
||||||
|
List[MineNote,],
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
我的世界通道信息类型
|
||||||
|
|
||||||
|
Dict[int,Dict[int,List[MineNote,],],]
|
||||||
|
"""
|
||||||
|
|
||||||
|
MineNoteTrackType = Mapping[
|
||||||
|
int,
|
||||||
|
List[MineNote,],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -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,35 +33,19 @@ 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 .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 .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(
|
||||||
@@ -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"=",
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
默认的进度条样式
|
|
||||||
"""
|
|
||||||
BIN
old-things/Packer/MSCT_MAIN.MPK
Normal file
BIN
old-things/Packer/MSCT_MAIN.MPK
Normal file
Binary file not shown.
BIN
old-things/Packer/MSCT_PLUGIN.MPK
Normal file
BIN
old-things/Packer/MSCT_PLUGIN.MPK
Normal file
Binary file not shown.
1
old-things/Packer/MSCT_PLUGIN_FUNCTION.MPK
Normal file
1
old-things/Packer/MSCT_PLUGIN_FUNCTION.MPK
Normal file
@@ -0,0 +1 @@
|
|||||||
|
q@v,fxіБ<D196>Еџ<D095>лцmЩ5]Ќs"ЏџЦбBMXi<58>ЈnНхч<D185>Z8О=Г<7F>4<EFBFBD>PTUБQЈmтфджG<D0B6>жu_цп<D186>DS№|
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
import Musicreater.experiment
|
import Musicreater.experiment
|
||||||
import Musicreater.plugin
|
import Musicreater.old_plugin
|
||||||
|
|
||||||
# import Musicreater.previous
|
# import Musicreater.previous
|
||||||
from Musicreater.plugin.addonpack import (
|
from Musicreater.old_plugin.addonpack import (
|
||||||
to_addon_pack_in_delay,
|
to_addon_pack_in_delay,
|
||||||
to_addon_pack_in_repeater,
|
to_addon_pack_in_repeater,
|
||||||
to_addon_pack_in_score,
|
to_addon_pack_in_score,
|
||||||
)
|
)
|
||||||
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
from Musicreater.old_plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||||
from Musicreater.plugin.mcstructfile import (
|
from Musicreater.old_plugin.mcstructfile import (
|
||||||
to_mcstructure_file_in_delay,
|
to_mcstructure_file_in_delay,
|
||||||
to_mcstructure_file_in_repeater,
|
to_mcstructure_file_in_repeater,
|
||||||
to_mcstructure_file_in_score,
|
to_mcstructure_file_in_score,
|
||||||
)
|
)
|
||||||
|
|
||||||
MSCT_MAIN = (
|
MSCT_MAIN = (
|
||||||
Musicreater,
|
old_init,
|
||||||
Musicreater.experiment,
|
old_init.experiment,
|
||||||
# Musicreater.previous,
|
# Musicreater.previous,
|
||||||
)
|
)
|
||||||
|
|
||||||
MSCT_PLUGIN = (Musicreater.plugin,)
|
MSCT_PLUGIN = (old_init.old_plugin,)
|
||||||
|
|
||||||
MSCT_PLUGIN_FUNCTION = (
|
MSCT_PLUGIN_FUNCTION = (
|
||||||
to_addon_pack_in_delay,
|
to_addon_pack_in_delay,
|
||||||
6
old-things/Packer/checksum.txt
Normal file
6
old-things/Packer/checksum.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
MSCT_MAIN:
|
||||||
|
6b9f5a97d50beb07c834e375104c67ae44c57ae40f73fb71075b3668899029c7
|
||||||
|
MSCT_PLUGIN:
|
||||||
|
c280413a394a539438a5d10078c9b55f04bcd4cf6869c59a3f7a026039748cfc
|
||||||
|
MSCT_PLUGIN_FUNCTION:
|
||||||
|
40697f1d9b293268fe142fa3e9bffee2923a8f4811ec7bbdf7b14afb98723ef2
|
||||||
0
old-things/bgArrayLib/__init__.py
Normal file
0
old-things/bgArrayLib/__init__.py
Normal file
67
old-things/bgArrayLib/bpm.py
Normal file
67
old-things/bgArrayLib/bpm.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import mido
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
'''
|
||||||
|
bpm
|
||||||
|
bites per minutes
|
||||||
|
每分钟的拍数
|
||||||
|
'''
|
||||||
|
|
||||||
|
def mt2gt(mt, tpb_a, bpm_a):
|
||||||
|
return round(mt / tpb_a / bpm_a * 60)
|
||||||
|
|
||||||
|
|
||||||
|
def get(mid:mido.MidiFile) -> int:
|
||||||
|
'''传入一个 MidiFile, 返回其音乐的bpm
|
||||||
|
:param mid : mido.MidFile
|
||||||
|
mido库识别的midi文件数据
|
||||||
|
:return bpm : int
|
||||||
|
'''
|
||||||
|
# mid = mido.MidiFile(mf)
|
||||||
|
length = mid.length
|
||||||
|
tpb = mid.ticks_per_beat
|
||||||
|
bpm = 20
|
||||||
|
gotV = 0
|
||||||
|
|
||||||
|
for track in mid.tracks:
|
||||||
|
global_time = 0
|
||||||
|
for msg in track:
|
||||||
|
global_time += msg.time
|
||||||
|
if msg.type == "note_on" and msg.velocity > 0:
|
||||||
|
gotV = mt2gt(global_time, tpb, bpm)
|
||||||
|
errorV = numpy.fabs(gotV - length)
|
||||||
|
last_dic = {bpm: errorV}
|
||||||
|
if last_dic.get(bpm) > errorV:
|
||||||
|
last_dic = {bpm: errorV}
|
||||||
|
bpm += 2
|
||||||
|
|
||||||
|
while True:
|
||||||
|
for track in mid.tracks:
|
||||||
|
global_time = 0
|
||||||
|
for msg in track:
|
||||||
|
global_time += msg.time
|
||||||
|
if msg.type == "note_on" and msg.velocity > 0:
|
||||||
|
gotV = mt2gt(global_time, tpb, bpm)
|
||||||
|
errorV = numpy.fabs(gotV - length)
|
||||||
|
try:
|
||||||
|
if last_dic.get(bpm - 2) > errorV:
|
||||||
|
last_dic = {bpm: errorV}
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
bpm += 2
|
||||||
|
if bpm >= 252:
|
||||||
|
break
|
||||||
|
print(list(last_dic.keys())[0])
|
||||||
|
return list(last_dic.keys())[0]
|
||||||
|
|
||||||
|
|
||||||
|
def compute(mid:mido.MidiFile):
|
||||||
|
answer = 60000000/mid.ticks_per_beat
|
||||||
|
print(answer)
|
||||||
|
return answer
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mid = mido.MidiFile(r"C:\Users\lc\Documents\MuseScore3\乐谱\乐谱\Bad style - Time back.mid")
|
||||||
|
get(mid)
|
||||||
|
compute(mid)
|
||||||
40
old-things/bgArrayLib/compute.py
Normal file
40
old-things/bgArrayLib/compute.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
def round_up(num, power=0):
|
||||||
|
"""
|
||||||
|
实现精确四舍五入,包含正、负小数多种场景
|
||||||
|
:param num: 需要四舍五入的小数
|
||||||
|
:param power: 四舍五入位数,支持0-∞
|
||||||
|
:return: 返回四舍五入后的结果
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
print(1 / 0)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
digit = 10 ** power
|
||||||
|
num2 = float(int(num * digit))
|
||||||
|
# 处理正数,power不为0的情况
|
||||||
|
if num >= 0 and power != 0:
|
||||||
|
tag = num * digit - num2 + 1 / (digit * 10)
|
||||||
|
if tag >= 0.5:
|
||||||
|
return (num2 + 1) / digit
|
||||||
|
else:
|
||||||
|
return num2 / digit
|
||||||
|
# 处理正数,power为0取整的情况
|
||||||
|
elif num >= 0 and power == 0:
|
||||||
|
tag = num * digit - int(num)
|
||||||
|
if tag >= 0.5:
|
||||||
|
return (num2 + 1) / digit
|
||||||
|
else:
|
||||||
|
return num2 / digit
|
||||||
|
# 处理负数,power为0取整的情况
|
||||||
|
elif power == 0 and num < 0:
|
||||||
|
tag = num * digit - int(num)
|
||||||
|
if tag <= -0.5:
|
||||||
|
return (num2 - 1) / digit
|
||||||
|
else:
|
||||||
|
return num2 / digit
|
||||||
|
# 处理负数,power不为0的情况
|
||||||
|
else:
|
||||||
|
tag = num * digit - num2 - 1 / (digit * 10)
|
||||||
|
if tag <= -0.5:
|
||||||
|
return (num2 - 1) / digit
|
||||||
|
else:
|
||||||
|
return num2 / digit
|
||||||
130
old-things/bgArrayLib/instrumentConstant.py
Normal file
130
old-things/bgArrayLib/instrumentConstant.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
instrument_list = {
|
||||||
|
"0": "harp",
|
||||||
|
"1": "harp",
|
||||||
|
"2": "pling",
|
||||||
|
"3": "harp",
|
||||||
|
"4": "pling",
|
||||||
|
"5": "pling",
|
||||||
|
"6": "harp",
|
||||||
|
"7": "harp",
|
||||||
|
"8": "share",
|
||||||
|
"9": "harp",
|
||||||
|
"10": "didgeridoo",
|
||||||
|
"11": "harp",
|
||||||
|
"12": "xylophone",
|
||||||
|
"13": "chime",
|
||||||
|
"14": "harp",
|
||||||
|
"15": "harp",
|
||||||
|
"16": "bass",
|
||||||
|
"17": "harp",
|
||||||
|
"18": "harp",
|
||||||
|
"19": "harp",
|
||||||
|
"20": "harp",
|
||||||
|
"21": "harp",
|
||||||
|
"22": "harp",
|
||||||
|
"23": "guitar",
|
||||||
|
"24": "guitar",
|
||||||
|
"25": "guitar",
|
||||||
|
"26": "guitar",
|
||||||
|
"27": "guitar",
|
||||||
|
"28": "guitar",
|
||||||
|
"29": "guitar",
|
||||||
|
"30": "guitar",
|
||||||
|
"31": "bass",
|
||||||
|
"32": "bass",
|
||||||
|
"33": "bass",
|
||||||
|
"34": "bass",
|
||||||
|
"35": "bass",
|
||||||
|
"36": "bass",
|
||||||
|
"37": "bass",
|
||||||
|
"38": "bass",
|
||||||
|
"39": "bass",
|
||||||
|
"40": "harp",
|
||||||
|
"41": "harp",
|
||||||
|
"42": "harp",
|
||||||
|
"43": "harp",
|
||||||
|
"44": "iron_xylophone",
|
||||||
|
"45": "guitar",
|
||||||
|
"46": "harp",
|
||||||
|
"47": "harp",
|
||||||
|
"48": "guitar",
|
||||||
|
"49": "guitar",
|
||||||
|
"50": "bit",
|
||||||
|
"51": "bit",
|
||||||
|
"52": "harp",
|
||||||
|
"53": "harp",
|
||||||
|
"54": "bit",
|
||||||
|
"55": "flute",
|
||||||
|
"56": "flute",
|
||||||
|
"57": "flute",
|
||||||
|
"58": "flute",
|
||||||
|
"59": "flute",
|
||||||
|
"60": "flute",
|
||||||
|
"61": "flute",
|
||||||
|
"62": "flute",
|
||||||
|
"63": "flute",
|
||||||
|
"64": "bit",
|
||||||
|
"65": "bit",
|
||||||
|
"66": "bit",
|
||||||
|
"67": "bit",
|
||||||
|
"68": "flute",
|
||||||
|
"69": "harp",
|
||||||
|
"70": "harp",
|
||||||
|
"71": "flute",
|
||||||
|
"72": "flute",
|
||||||
|
"73": "flute",
|
||||||
|
"74": "harp",
|
||||||
|
"75": "flute",
|
||||||
|
"76": "harp",
|
||||||
|
"77": "harp",
|
||||||
|
"78": "harp",
|
||||||
|
"79": "harp",
|
||||||
|
"80": "bit",
|
||||||
|
"81": "bit",
|
||||||
|
"82": "bit",
|
||||||
|
"83": "bit",
|
||||||
|
"84": "bit",
|
||||||
|
"85": "bit",
|
||||||
|
"86": "bit",
|
||||||
|
"87": "bit",
|
||||||
|
"88": "bit",
|
||||||
|
"89": "bit",
|
||||||
|
"90": "bit",
|
||||||
|
"91": "bit",
|
||||||
|
"92": "bit",
|
||||||
|
"93": "bit",
|
||||||
|
"94": "bit",
|
||||||
|
"95": "bit",
|
||||||
|
"96": "bit",
|
||||||
|
"97": "bit",
|
||||||
|
"98": "bit",
|
||||||
|
"99": "bit",
|
||||||
|
"100": "bit",
|
||||||
|
"101": "bit",
|
||||||
|
"102": "bit",
|
||||||
|
"103": "bit",
|
||||||
|
"104": "harp",
|
||||||
|
"105": "banjo",
|
||||||
|
"106": "harp",
|
||||||
|
"107": "harp",
|
||||||
|
"108": "harp",
|
||||||
|
"109": "harp",
|
||||||
|
"110": "harp",
|
||||||
|
"111": "guitar",
|
||||||
|
"112": "harp",
|
||||||
|
"113": "bell",
|
||||||
|
"114": "harp",
|
||||||
|
"115": "cow_bell",
|
||||||
|
"116": "basedrum",
|
||||||
|
"117": "bass",
|
||||||
|
"118": "bit",
|
||||||
|
"119": "basedrum",
|
||||||
|
"120": "guitar",
|
||||||
|
"121": "harp",
|
||||||
|
"122": "harp",
|
||||||
|
"123": "harp",
|
||||||
|
"124": "harp",
|
||||||
|
"125": "hat",
|
||||||
|
"126": "basedrum",
|
||||||
|
"127": "snare",
|
||||||
|
}
|
||||||
250
old-things/bgArrayLib/namesConstant.py
Normal file
250
old-things/bgArrayLib/namesConstant.py
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
zip_name = {
|
||||||
|
-1: "-1.Acoustic_Kit_打击乐.zip",
|
||||||
|
0: "0.Acoustic_Grand_Piano_大钢琴.zip",
|
||||||
|
1: "1.Bright_Acoustic_Piano_亮音大钢琴.zip",
|
||||||
|
10: "10.Music_Box_八音盒.zip",
|
||||||
|
100: "100.FX_brightness_合成特效-亮音.zip",
|
||||||
|
101: "101.FX_goblins_合成特效-小妖.zip",
|
||||||
|
102: "102.FX_echoes_合成特效-回声.zip",
|
||||||
|
103: "103.FX_sci-fi_合成特效-科幻.zip",
|
||||||
|
104: "104.Sitar_锡塔尔.zip",
|
||||||
|
105: "105.Banjo_班卓.zip",
|
||||||
|
106: "106.Shamisen_三味线.zip",
|
||||||
|
107: "107.Koto_筝.zip",
|
||||||
|
108: "108.Kalimba_卡林巴.zip",
|
||||||
|
109: "109.Bagpipe_风笛.zip",
|
||||||
|
11: "11.Vibraphone_电颤琴.zip",
|
||||||
|
110: "110.Fiddle_古提琴.zip",
|
||||||
|
111: "111.Shanai_唢呐.zip",
|
||||||
|
112: "112.Tinkle_Bell_铃铛.zip",
|
||||||
|
113: "113.Agogo_拉丁打铃.zip",
|
||||||
|
114: "114.Steel_Drums_钢鼓.zip",
|
||||||
|
115: "115.Woodblock_木块.zip",
|
||||||
|
116: "116.Taiko_Drum_太鼓.zip",
|
||||||
|
117: "117.Melodic_Tom_嗵鼓.zip",
|
||||||
|
118: "118.Synth_Drum_合成鼓.zip",
|
||||||
|
119: "119.Reverse_Cymbal_镲波形反转.zip",
|
||||||
|
12: "12.Marimba_马林巴.zip",
|
||||||
|
13: "13.Xylophone_木琴.zip",
|
||||||
|
14: "14.Tubular_Bells_管钟.zip",
|
||||||
|
15: "15.Dulcimer_扬琴.zip",
|
||||||
|
16: "16.Drawbar_Organ_击杆风琴.zip",
|
||||||
|
17: "17.Percussive_Organ_打击型风琴.zip",
|
||||||
|
18: "18.Rock_Organ_摇滚风琴.zip",
|
||||||
|
19: "19.Church_Organ_管风琴.zip",
|
||||||
|
2: "2.Electric_Grand_Piano_电子大钢琴.zip",
|
||||||
|
20: "20.Reed_Organ_簧风琴.zip",
|
||||||
|
21: "21.Accordion_手风琴.zip",
|
||||||
|
22: "22.Harmonica_口琴.zip",
|
||||||
|
23: "23.Tango_Accordian_探戈手风琴.zip",
|
||||||
|
24: "24.Acoustic_Guitar_(nylon)_尼龙弦吉他.zip",
|
||||||
|
25: "25.Acoustic_Guitar(steel)_钢弦吉他.zip",
|
||||||
|
26: "26.Electric_Guitar_(jazz)_爵士乐电吉他.zip",
|
||||||
|
27: "27.Electric_Guitar_(clean)_清音电吉他.zip",
|
||||||
|
28: "28.Electric_Guitar_(muted)_弱音电吉他.zip",
|
||||||
|
29: "29.Overdriven_Guitar_驱动音效吉他.zip",
|
||||||
|
3: "3.Honky-Tonk_Piano_酒吧钢琴.zip",
|
||||||
|
30: "30.Distortion_Guitar_失真音效吉他.zip",
|
||||||
|
31: "31.Guitar_Harmonics_吉他泛音.zip",
|
||||||
|
32: "32.Acoustic_Bass_原声贝司.zip",
|
||||||
|
33: "33.Electric_Bass(finger)_指拨电贝司.zip",
|
||||||
|
34: "34.Electric_Bass(pick)_拨片拨电贝司.zip",
|
||||||
|
35: "35.Fretless_Bass_无品贝司.zip",
|
||||||
|
36: "36.Slap_Bass_A_击弦贝司A.zip",
|
||||||
|
37: "37.Slap_Bass_B_击弦贝司B.zip",
|
||||||
|
38: "38.Synth_Bass_A_合成贝司A.zip",
|
||||||
|
39: "39.Synth_Bass_B_合成贝司B.zip",
|
||||||
|
4: "4.Electric_Piano_1_电钢琴A.zip",
|
||||||
|
40: "40.Violin_小提琴.zip",
|
||||||
|
41: "41.Viola_中提琴.zip",
|
||||||
|
42: "42.Cello_大提琴.zip",
|
||||||
|
43: "43.Contrabass_低音提琴.zip",
|
||||||
|
44: "44.Tremolo_Strings_弦乐震音.zip",
|
||||||
|
45: "45.Pizzicato_Strings_弦乐拨奏.zip",
|
||||||
|
46: "46.Orchestral_Harp_竖琴.zip",
|
||||||
|
47: "47.Timpani_定音鼓.zip",
|
||||||
|
48: "48.String_Ensemble_A_弦乐合奏A.zip",
|
||||||
|
49: "49.String_Ensemble_B_弦乐合奏B.zip",
|
||||||
|
5: "5.Electric_Piano_2_电钢琴B.zip",
|
||||||
|
50: "50.SynthStrings_A_合成弦乐A.zip",
|
||||||
|
51: "51.SynthStrings_B_合成弦乐B.zip",
|
||||||
|
52: "52.Choir_Aahs_合唱“啊”音.zip",
|
||||||
|
53: "53.Voice_Oohs_人声“哦”音.zip",
|
||||||
|
54: "54.Synth_Voice_合成人声.zip",
|
||||||
|
55: "55.Orchestra_Hit_乐队打击乐.zip",
|
||||||
|
56: "56.Trumpet_小号.zip",
|
||||||
|
57: "57.Trombone_长号.zip",
|
||||||
|
58: "58.Tuba_大号.zip",
|
||||||
|
59: "59.Muted_Trumpet_弱音小号.zip",
|
||||||
|
6: "6.Harpsichord_拨弦古钢琴.zip",
|
||||||
|
60: "60.French_Horn_圆号.zip",
|
||||||
|
61: "61.Brass_Section_铜管组.zip",
|
||||||
|
62: "62.Synth_Brass_A_合成铜管A.zip",
|
||||||
|
63: "63.Synth_Brass_A_合成铜管B.zip",
|
||||||
|
64: "64.Soprano_Sax_高音萨克斯.zip",
|
||||||
|
65: "65.Alto_Sax_中音萨克斯.zip",
|
||||||
|
66: "66.Tenor_Sax_次中音萨克斯.zip",
|
||||||
|
67: "67.Baritone_Sax_上低音萨克斯.zip",
|
||||||
|
68: "68.Oboe_双簧管.zip",
|
||||||
|
69: "69.English_Horn_英国管.zip",
|
||||||
|
7: "7.Clavinet_击弦古钢琴.zip",
|
||||||
|
70: "70.Bassoon_大管.zip",
|
||||||
|
71: "71.Clarinet_单簧管.zip",
|
||||||
|
72: "72.Piccolo_短笛.zip",
|
||||||
|
73: "73.Flute_长笛.zip",
|
||||||
|
74: "74.Recorder_竖笛.zip",
|
||||||
|
75: "75.Pan_Flute_排笛.zip",
|
||||||
|
76: "76.Bottle_Blow_吹瓶口.zip",
|
||||||
|
77: "77.Skakuhachi_尺八.zip",
|
||||||
|
78: "78.Whistle_哨.zip",
|
||||||
|
79: "79.Ocarina_洋埙.zip",
|
||||||
|
8: "8.Celesta_钢片琴.zip",
|
||||||
|
80: "80.Lead_square_合成主音-方波.zip",
|
||||||
|
81: "81.Lead_sawtooth_合成主音-锯齿波.zip",
|
||||||
|
82: "82.Lead_calliope_lead_合成主音-汽笛风琴.zip",
|
||||||
|
83: "83.Lead_chiff_lead_合成主音-吹管.zip",
|
||||||
|
84: "84.Lead_charang_合成主音5-吉他.zip",
|
||||||
|
85: "85.Lead_voice_合成主音-人声.zip",
|
||||||
|
86: "86.Lead_fifths_合成主音-五度.zip",
|
||||||
|
87: "87.Lead_bass+lead_合成主音-低音加主音.zip",
|
||||||
|
88: "88.Pad_new_age_合成柔音-新时代.zip",
|
||||||
|
89: "89.Pad_warm_合成柔音-暖音.zip",
|
||||||
|
9: "9.Glockenspiel_钟琴.zip",
|
||||||
|
90: "90.Pad_polysynth_合成柔音-复合成.zip",
|
||||||
|
91: "91.Pad_choir_合成柔音-合唱.zip",
|
||||||
|
92: "92.Pad_bowed_合成柔音-弓弦.zip",
|
||||||
|
93: "93.Pad_metallic_合成柔音-金属.zip",
|
||||||
|
94: "94.Pad_halo_合成柔音-光环.zip",
|
||||||
|
95: "95.Pad_sweep_合成柔音-扫弦.zip",
|
||||||
|
96: "96.FX_rain_合成特效-雨.zip",
|
||||||
|
97: "97.FX_soundtrack_合成特效-音轨.zip",
|
||||||
|
98: "98.FX_crystal_合成特效-水晶.zip",
|
||||||
|
99: "99.FX_atmosphere_合成特效-大气.zip",
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpack_name = {
|
||||||
|
-1: "-1.Acoustic_Kit_打击乐.mcpack",
|
||||||
|
0: "0.Acoustic_Grand_Piano_大钢琴.mcpack",
|
||||||
|
1: "1.Bright_Acoustic_Piano_亮音大钢琴.mcpack",
|
||||||
|
10: "10.Music_Box_八音盒.mcpack",
|
||||||
|
100: "100.FX_brightness_合成特效-亮音.mcpack",
|
||||||
|
101: "101.FX_goblins_合成特效-小妖.mcpack",
|
||||||
|
102: "102.FX_echoes_合成特效-回声.mcpack",
|
||||||
|
103: "103.FX_sci-fi_合成特效-科幻.mcpack",
|
||||||
|
104: "104.Sitar_锡塔尔.mcpack",
|
||||||
|
105: "105.Banjo_班卓.mcpack",
|
||||||
|
106: "106.Shamisen_三味线.mcpack",
|
||||||
|
107: "107.Koto_筝.mcpack",
|
||||||
|
108: "108.Kalimba_卡林巴.mcpack",
|
||||||
|
109: "109.Bagpipe_风笛.mcpack",
|
||||||
|
11: "11.Vibraphone_电颤琴.mcpack",
|
||||||
|
110: "110.Fiddle_古提琴.mcpack",
|
||||||
|
111: "111.Shanai_唢呐.mcpack",
|
||||||
|
112: "112.Tinkle_Bell_铃铛.mcpack",
|
||||||
|
113: "113.Agogo_拉丁打铃.mcpack",
|
||||||
|
114: "114.Steel_Drums_钢鼓.mcpack",
|
||||||
|
115: "115.Woodblock_木块.mcpack",
|
||||||
|
116: "116.Taiko_Drum_太鼓.mcpack",
|
||||||
|
117: "117.Melodic_Tom_嗵鼓.mcpack",
|
||||||
|
118: "118.Synth_Drum_合成鼓.mcpack",
|
||||||
|
119: "119.Reverse_Cymbal_镲波形反转.mcpack",
|
||||||
|
12: "12.Marimba_马林巴.mcpack",
|
||||||
|
13: "13.Xylophone_木琴.mcpack",
|
||||||
|
14: "14.Tubular_Bells_管钟.mcpack",
|
||||||
|
15: "15.Dulcimer_扬琴.mcpack",
|
||||||
|
16: "16.Drawbar_Organ_击杆风琴.mcpack",
|
||||||
|
17: "17.Percussive_Organ_打击型风琴.mcpack",
|
||||||
|
18: "18.Rock_Organ_摇滚风琴.mcpack",
|
||||||
|
19: "19.Church_Organ_管风琴.mcpack",
|
||||||
|
2: "2.Electric_Grand_Piano_电子大钢琴.mcpack",
|
||||||
|
20: "20.Reed_Organ_簧风琴.mcpack",
|
||||||
|
21: "21.Accordion_手风琴.mcpack",
|
||||||
|
22: "22.Harmonica_口琴.mcpack",
|
||||||
|
23: "23.Tango_Accordian_探戈手风琴.mcpack",
|
||||||
|
24: "24.Acoustic_Guitar_(nylon)_尼龙弦吉他.mcpack",
|
||||||
|
25: "25.Acoustic_Guitar(steel)_钢弦吉他.mcpack",
|
||||||
|
26: "26.Electric_Guitar_(jazz)_爵士乐电吉他.mcpack",
|
||||||
|
27: "27.Electric_Guitar_(clean)_清音电吉他.mcpack",
|
||||||
|
28: "28.Electric_Guitar_(muted)_弱音电吉他.mcpack",
|
||||||
|
29: "29.Overdriven_Guitar_驱动音效吉他.mcpack",
|
||||||
|
3: "3.Honky-Tonk_Piano_酒吧钢琴.mcpack",
|
||||||
|
30: "30.Distortion_Guitar_失真音效吉他.mcpack",
|
||||||
|
31: "31.Guitar_Harmonics_吉他泛音.mcpack",
|
||||||
|
32: "32.Acoustic_Bass_原声贝司.mcpack",
|
||||||
|
33: "33.Electric_Bass(finger)_指拨电贝司.mcpack",
|
||||||
|
34: "34.Electric_Bass(pick)_拨片拨电贝司.mcpack",
|
||||||
|
35: "35.Fretless_Bass_无品贝司.mcpack",
|
||||||
|
36: "36.Slap_Bass_A_击弦贝司A.mcpack",
|
||||||
|
37: "37.Slap_Bass_B_击弦贝司B.mcpack",
|
||||||
|
38: "38.Synth_Bass_A_合成贝司A.mcpack",
|
||||||
|
39: "39.Synth_Bass_B_合成贝司B.mcpack",
|
||||||
|
4: "4.Electric_Piano_1_电钢琴A.mcpack",
|
||||||
|
40: "40.Violin_小提琴.mcpack",
|
||||||
|
41: "41.Viola_中提琴.mcpack",
|
||||||
|
42: "42.Cello_大提琴.mcpack",
|
||||||
|
43: "43.Contrabass_低音提琴.mcpack",
|
||||||
|
44: "44.Tremolo_Strings_弦乐震音.mcpack",
|
||||||
|
45: "45.Pizzicato_Strings_弦乐拨奏.mcpack",
|
||||||
|
46: "46.Orchestral_Harp_竖琴.mcpack",
|
||||||
|
47: "47.Timpani_定音鼓.mcpack",
|
||||||
|
48: "48.String_Ensemble_A_弦乐合奏A.mcpack",
|
||||||
|
49: "49.String_Ensemble_B_弦乐合奏B.mcpack",
|
||||||
|
5: "5.Electric_Piano_2_电钢琴B.mcpack",
|
||||||
|
50: "50.SynthStrings_A_合成弦乐A.mcpack",
|
||||||
|
51: "51.SynthStrings_B_合成弦乐B.mcpack",
|
||||||
|
52: "52.Choir_Aahs_合唱“啊”音.mcpack",
|
||||||
|
53: "53.Voice_Oohs_人声“哦”音.mcpack",
|
||||||
|
54: "54.Synth_Voice_合成人声.mcpack",
|
||||||
|
55: "55.Orchestra_Hit_乐队打击乐.mcpack",
|
||||||
|
56: "56.Trumpet_小号.mcpack",
|
||||||
|
57: "57.Trombone_长号.mcpack",
|
||||||
|
58: "58.Tuba_大号.mcpack",
|
||||||
|
59: "59.Muted_Trumpet_弱音小号.mcpack",
|
||||||
|
6: "6.Harpsichord_拨弦古钢琴.mcpack",
|
||||||
|
60: "60.French_Horn_圆号.mcpack",
|
||||||
|
61: "61.Brass_Section_铜管组.mcpack",
|
||||||
|
62: "62.Synth_Brass_A_合成铜管A.mcpack",
|
||||||
|
63: "63.Synth_Brass_A_合成铜管B.mcpack",
|
||||||
|
64: "64.Soprano_Sax_高音萨克斯.mcpack",
|
||||||
|
65: "65.Alto_Sax_中音萨克斯.mcpack",
|
||||||
|
66: "66.Tenor_Sax_次中音萨克斯.mcpack",
|
||||||
|
67: "67.Baritone_Sax_上低音萨克斯.mcpack",
|
||||||
|
68: "68.Oboe_双簧管.mcpack",
|
||||||
|
69: "69.English_Horn_英国管.mcpack",
|
||||||
|
7: "7.Clavinet_击弦古钢琴.mcpack",
|
||||||
|
70: "70.Bassoon_大管.mcpack",
|
||||||
|
71: "71.Clarinet_单簧管.mcpack",
|
||||||
|
72: "72.Piccolo_短笛.mcpack",
|
||||||
|
73: "73.Flute_长笛.mcpack",
|
||||||
|
74: "74.Recorder_竖笛.mcpack",
|
||||||
|
75: "75.Pan_Flute_排笛.mcpack",
|
||||||
|
76: "76.Bottle_Blow_吹瓶口.mcpack",
|
||||||
|
77: "77.Skakuhachi_尺八.mcpack",
|
||||||
|
78: "78.Whistle_哨.mcpack",
|
||||||
|
79: "79.Ocarina_洋埙.mcpack",
|
||||||
|
8: "8.Celesta_钢片琴.mcpack",
|
||||||
|
80: "80.Lead_square_合成主音-方波.mcpack",
|
||||||
|
81: "81.Lead_sawtooth_合成主音-锯齿波.mcpack",
|
||||||
|
82: "82.Lead_calliope_lead_合成主音-汽笛风琴.mcpack",
|
||||||
|
83: "83.Lead_chiff_lead_合成主音-吹管.mcpack",
|
||||||
|
84: "84.Lead_charang_合成主音5-吉他.mcpack",
|
||||||
|
85: "85.Lead_voice_合成主音-人声.mcpack",
|
||||||
|
86: "86.Lead_fifths_合成主音-五度.mcpack",
|
||||||
|
87: "87.Lead_bass+lead_合成主音-低音加主音.mcpack",
|
||||||
|
88: "88.Pad_new_age_合成柔音-新时代.mcpack",
|
||||||
|
89: "89.Pad_warm_合成柔音-暖音.mcpack",
|
||||||
|
9: "9.Glockenspiel_钟琴.mcpack",
|
||||||
|
90: "90.Pad_polysynth_合成柔音-复合成.mcpack",
|
||||||
|
91: "91.Pad_choir_合成柔音-合唱.mcpack",
|
||||||
|
92: "92.Pad_bowed_合成柔音-弓弦.mcpack",
|
||||||
|
93: "93.Pad_metallic_合成柔音-金属.mcpack",
|
||||||
|
94: "94.Pad_halo_合成柔音-光环.mcpack",
|
||||||
|
95: "95.Pad_sweep_合成柔音-扫弦.mcpack",
|
||||||
|
96: "96.FX_rain_合成特效-雨.mcpack",
|
||||||
|
97: "97.FX_soundtrack_合成特效-音轨.mcpack",
|
||||||
|
98: "98.FX_crystal_合成特效-水晶.mcpack",
|
||||||
|
99: "99.FX_atmosphere_合成特效-大气.mcpack",
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(zip_name[0])
|
||||||
134
old-things/bgArrayLib/pitchStrConstant.py
Normal file
134
old-things/bgArrayLib/pitchStrConstant.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
pitch = {
|
||||||
|
"0": "0.0220970869120796",
|
||||||
|
"1": "0.0234110480761981",
|
||||||
|
"2": "0.0248031414370031",
|
||||||
|
"3": "0.0262780129766786",
|
||||||
|
"4": "0.0278405849418856",
|
||||||
|
"5": "0.0294960722713029",
|
||||||
|
"6": "0.03125",
|
||||||
|
"7": "0.033108221698728",
|
||||||
|
"8": "0.0350769390096679",
|
||||||
|
"9": "0.037162722343835",
|
||||||
|
"10": "0.0393725328092148",
|
||||||
|
"11": "0.0417137454428136",
|
||||||
|
"12": "0.0441941738241592",
|
||||||
|
"13": "0.0468220961523963",
|
||||||
|
"14": "0.0496062828740062",
|
||||||
|
"15": "0.0525560259533572",
|
||||||
|
"16": "0.0556811698837712",
|
||||||
|
"17": "0.0589921445426059",
|
||||||
|
"18": "0.0625",
|
||||||
|
"19": "0.066216443397456",
|
||||||
|
"20": "0.0701538780193358",
|
||||||
|
"21": "0.0743254446876701",
|
||||||
|
"22": "0.0787450656184296",
|
||||||
|
"23": "0.0834274908856271",
|
||||||
|
"24": "0.0883883476483184",
|
||||||
|
"25": "0.0936441923047926",
|
||||||
|
"26": "0.0992125657480125",
|
||||||
|
"27": "0.105112051906714",
|
||||||
|
"28": "0.111362339767542",
|
||||||
|
"29": "0.117984289085212",
|
||||||
|
"30": "0.125",
|
||||||
|
"31": "0.132432886794912",
|
||||||
|
"32": "0.140307756038672",
|
||||||
|
"33": "0.14865088937534",
|
||||||
|
"34": "0.157490131236859",
|
||||||
|
"35": "0.166854981771254",
|
||||||
|
"36": "0.176776695296637",
|
||||||
|
"37": "0.187288384609585",
|
||||||
|
"38": "0.198425131496025",
|
||||||
|
"39": "0.210224103813429",
|
||||||
|
"40": "0.222724679535085",
|
||||||
|
"41": "0.235968578170423",
|
||||||
|
"42": "0.25",
|
||||||
|
"43": "0.264865773589824",
|
||||||
|
"44": "0.280615512077343",
|
||||||
|
"45": "0.29730177875068",
|
||||||
|
"46": "0.314980262473718",
|
||||||
|
"47": "0.333709963542509",
|
||||||
|
"48": "0.353553390593274",
|
||||||
|
"49": "0.37457676921917",
|
||||||
|
"50": "0.39685026299205",
|
||||||
|
"51": "0.420448207626857",
|
||||||
|
"52": "0.44544935907017",
|
||||||
|
"53": "0.471937156340847",
|
||||||
|
"54": "0.5",
|
||||||
|
"55": "0.529731547179648",
|
||||||
|
"56": "0.561231024154687",
|
||||||
|
"57": "0.594603557501361",
|
||||||
|
"58": "0.629960524947437",
|
||||||
|
"59": "0.667419927085017",
|
||||||
|
"60": "0.707106781186548",
|
||||||
|
"61": "0.749153538438341",
|
||||||
|
"62": "0.7937005259841",
|
||||||
|
"63": "0.840896415253715",
|
||||||
|
"64": "0.890898718140339",
|
||||||
|
"65": "0.943874312681694",
|
||||||
|
"66": "1",
|
||||||
|
"67": "1.0594630943593",
|
||||||
|
"68": "1.12246204830937",
|
||||||
|
"69": "1.18920711500272",
|
||||||
|
"70": "1.25992104989487",
|
||||||
|
"71": "1.33483985417003",
|
||||||
|
"72": "1.4142135623731",
|
||||||
|
"73": "1.49830707687668",
|
||||||
|
"74": "1.5874010519682",
|
||||||
|
"75": "1.68179283050743",
|
||||||
|
"76": "1.78179743628068",
|
||||||
|
"77": "1.88774862536339",
|
||||||
|
"78": "2",
|
||||||
|
"79": "2.11892618871859",
|
||||||
|
"80": "2.24492409661875",
|
||||||
|
"81": "2.37841423000544",
|
||||||
|
"82": "2.51984209978975",
|
||||||
|
"83": "2.66967970834007",
|
||||||
|
"84": "2.82842712474619",
|
||||||
|
"85": "2.99661415375336",
|
||||||
|
"86": "3.1748021039364",
|
||||||
|
"87": "3.36358566101486",
|
||||||
|
"88": "3.56359487256136",
|
||||||
|
"89": "3.77549725072677",
|
||||||
|
"90": "4",
|
||||||
|
"91": "4.23785237743718",
|
||||||
|
"92": "4.48984819323749",
|
||||||
|
"93": "4.75682846001088",
|
||||||
|
"94": "5.03968419957949",
|
||||||
|
"95": "5.33935941668014",
|
||||||
|
"96": "5.65685424949238",
|
||||||
|
"97": "5.99322830750673",
|
||||||
|
"98": "6.3496042078728",
|
||||||
|
"99": "6.72717132202972",
|
||||||
|
"100": "7.12718974512272",
|
||||||
|
"101": "7.55099450145355",
|
||||||
|
"102": "8",
|
||||||
|
"103": "8.47570475487436",
|
||||||
|
"104": "8.97969638647498",
|
||||||
|
"105": "9.51365692002177",
|
||||||
|
"106": "10.079368399159",
|
||||||
|
"107": "10.6787188333603",
|
||||||
|
"108": "11.3137084989848",
|
||||||
|
"109": "11.9864566150135",
|
||||||
|
"110": "12.6992084157456",
|
||||||
|
"111": "13.4543426440594",
|
||||||
|
"112": "14.2543794902454",
|
||||||
|
"113": "15.1019890029071",
|
||||||
|
"114": "16",
|
||||||
|
"115": "16.9514095097487",
|
||||||
|
"116": "17.95939277295",
|
||||||
|
"117": "19.0273138400435",
|
||||||
|
"118": "20.158736798318",
|
||||||
|
"119": "21.3574376667206",
|
||||||
|
"120": "22.6274169979695",
|
||||||
|
"121": "23.9729132300269",
|
||||||
|
"122": "25.3984168314912",
|
||||||
|
"123": "26.9086852881189",
|
||||||
|
"124": "28.5087589804909",
|
||||||
|
"125": "30.2039780058142",
|
||||||
|
"126": "32",
|
||||||
|
"127": "33.9028190194974",
|
||||||
|
"128": "35.9187855458999",
|
||||||
|
"129": "38.0546276800871",
|
||||||
|
"130": "40.3174735966359",
|
||||||
|
"131": "42.7148753334411",
|
||||||
|
}
|
||||||
208
old-things/bgArrayLib/reader.py
Normal file
208
old-things/bgArrayLib/reader.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
# from nmcsup.log import log
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
|
||||||
|
class Note:
|
||||||
|
def __init__(self, channel, pitch, velocity, time, time_position, instrument):
|
||||||
|
self.channel = channel
|
||||||
|
self.pitch = pitch
|
||||||
|
self.velocity = velocity
|
||||||
|
self.delay = time
|
||||||
|
self.time_position = time_position
|
||||||
|
self.instrument = instrument
|
||||||
|
self.CD = "d"
|
||||||
|
|
||||||
|
def get_CD(self, start, end):
|
||||||
|
if end - start > 1.00:
|
||||||
|
self.CD = "c"
|
||||||
|
else:
|
||||||
|
self.CD = "d"
|
||||||
|
|
||||||
|
|
||||||
|
def midiNewReader(midfile: str):
|
||||||
|
import mido
|
||||||
|
|
||||||
|
# from msctspt.threadOpera import NewThread
|
||||||
|
from bgArrayLib.bpm import get
|
||||||
|
|
||||||
|
def Time(mt, tpb_a, bpm_a):
|
||||||
|
return round(mt / tpb_a / bpm_a * 60 * 20)
|
||||||
|
|
||||||
|
Notes = []
|
||||||
|
tracks = []
|
||||||
|
note_list = []
|
||||||
|
close = []
|
||||||
|
on = []
|
||||||
|
off = []
|
||||||
|
instruments = []
|
||||||
|
isPercussion = False
|
||||||
|
try:
|
||||||
|
mid = mido.MidiFile(midfile)
|
||||||
|
except Exception:
|
||||||
|
print("找不到文件或无法读取文件" + midfile)
|
||||||
|
return False
|
||||||
|
tpb = mid.ticks_per_beat
|
||||||
|
bpm = get(mid)
|
||||||
|
# 解析
|
||||||
|
# def loadMidi(track1):
|
||||||
|
for track in mid.tracks:
|
||||||
|
overallTime = 0.0
|
||||||
|
instrument = 0
|
||||||
|
for i in track:
|
||||||
|
overallTime += i.time
|
||||||
|
try:
|
||||||
|
if i.channel != 9:
|
||||||
|
# try:
|
||||||
|
# log("event_type(事件): " + str(i.type) + " channel(音轨): " + str(i.channel) +
|
||||||
|
# " note/pitch(音高): " +
|
||||||
|
# str(i[2]) +
|
||||||
|
# " velocity(力度): " + str(i.velocity) + " time(间隔时间): " + str(i.time) +
|
||||||
|
# " overallTime/globalTime/timePosition: " + str(overallTime) + " \n")
|
||||||
|
# except AttributeError:
|
||||||
|
# log("event_type(事件): " + str(i.type) + " thing(内容):" + str(i) + " \n")
|
||||||
|
if "program_change" in str(i):
|
||||||
|
instrument = i.program
|
||||||
|
if instrument > 119: # 音色不够
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
instruments.append(i.program)
|
||||||
|
if "note_on" in str(i) and i.velocity > 0:
|
||||||
|
print(i)
|
||||||
|
# print(i.note)
|
||||||
|
# print([Note(i.channel, i.note, i.velocity, i.time, Time(overallTime, tpb, bpm), instrument)])
|
||||||
|
tracks.append(
|
||||||
|
[
|
||||||
|
Note(
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
instrument,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
note_list.append(
|
||||||
|
[
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
instrument,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
on.append([i.note, Time(overallTime, tpb, bpm)])
|
||||||
|
# return [Note(i.channel, i, i.velocity, i.time, Time(overallTime, tpb, bpm))]
|
||||||
|
if "note_off" in str(i) or "note_on" in str(i) and i.velocity == 0:
|
||||||
|
# print(i)
|
||||||
|
# print([Note(i.channel, i.note, i.velocity, i.time, Time(overallTime, tpb, bpm))])
|
||||||
|
close.append(
|
||||||
|
[
|
||||||
|
Note(
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
instrument,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
off.append([i.note, Time(overallTime, tpb, bpm)])
|
||||||
|
# return [Note(i.channel, i, i.velocity, i.time, Time(overallTime, tpb, bpm))]
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
if "note_on" in str(i) and i.channel == 9:
|
||||||
|
if "note_on" in str(i) and i.velocity > 0:
|
||||||
|
print(i)
|
||||||
|
# print(i.note)
|
||||||
|
# print([Note(i.channel, i.note, i.velocity, i.time, Time(overallTime, tpb, bpm), -1)])
|
||||||
|
tracks.append(
|
||||||
|
[
|
||||||
|
Note(
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
note_list.append(
|
||||||
|
[
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
-1,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
on.append([i.note, Time(overallTime, tpb, bpm)])
|
||||||
|
isPercussion = True
|
||||||
|
# return [Note(i.channel, i, i.velocity, i.time, Time(overallTime, tpb, bpm))]
|
||||||
|
Notes.append(tracks)
|
||||||
|
if instruments is []:
|
||||||
|
instruments.append(0)
|
||||||
|
instruments = list(set(instruments))
|
||||||
|
with open("1.pkl", "wb") as b:
|
||||||
|
pickle.dump([instruments, isPercussion], b)
|
||||||
|
|
||||||
|
# for j, track in enumerate(mid.tracks):
|
||||||
|
# th = NewThread(loadMidi, (track,))
|
||||||
|
# th.start()
|
||||||
|
# Notes.append(th.getResult())
|
||||||
|
|
||||||
|
# print(Notes)
|
||||||
|
print(Notes.__len__())
|
||||||
|
# print(note_list)
|
||||||
|
print(instruments)
|
||||||
|
return Notes
|
||||||
|
# return [Notes, note_list]
|
||||||
|
|
||||||
|
|
||||||
|
def midiClassReader(midfile: str):
|
||||||
|
import mido
|
||||||
|
|
||||||
|
from bgArrayLib.bpm import get
|
||||||
|
|
||||||
|
def Time(mt, tpb_a, bpm_a):
|
||||||
|
return round(mt / tpb_a / bpm_a * 60 * 20)
|
||||||
|
|
||||||
|
Notes = []
|
||||||
|
tracks = []
|
||||||
|
try:
|
||||||
|
mid = mido.MidiFile(filename=midfile, clip=True)
|
||||||
|
except Exception:
|
||||||
|
print("找不到文件或无法读取文件" + midfile)
|
||||||
|
return False
|
||||||
|
print("midi已经载入了。")
|
||||||
|
tpb = mid.ticks_per_beat
|
||||||
|
bpm = get(mid)
|
||||||
|
for track in mid.tracks:
|
||||||
|
overallTime = 0.0
|
||||||
|
instrument = 0
|
||||||
|
for i in track:
|
||||||
|
overallTime += i.time
|
||||||
|
if "note_on" in str(i) and i.velocity > 0:
|
||||||
|
print(i)
|
||||||
|
tracks.append(
|
||||||
|
[
|
||||||
|
Note(
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
instrument,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
Notes.append(tracks)
|
||||||
|
print(Notes.__len__())
|
||||||
|
return Notes
|
||||||
147
old-things/bgArrayLib/sy_resourcesPacker.py
Normal file
147
old-things/bgArrayLib/sy_resourcesPacker.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# import tkinter.filedialog
|
||||||
|
# from namesConstant import zip_name
|
||||||
|
# from namesConstant import mcpack_name
|
||||||
|
import bgArrayLib.namesConstant
|
||||||
|
|
||||||
|
zipN = bgArrayLib.namesConstant.zip_name
|
||||||
|
mpN = bgArrayLib.namesConstant.mcpack_name
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
"format_version": 1,
|
||||||
|
"header": {
|
||||||
|
"name": "羽音缭绕-midiout_25.5--音创使用",
|
||||||
|
"description": "羽音缭绕-midiout_25.0--音创使用",
|
||||||
|
"uuid": "c1adbda4-3b3e-4e5b-a57e-cde8ac80ee19",
|
||||||
|
"version": [25, 5, 0],
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"description": "羽音缭绕-midiout_25.0--音创使用",
|
||||||
|
"type": "resources",
|
||||||
|
"uuid": "c13455d5-b9f3-47f2-9706-c05ad86b3180 ",
|
||||||
|
"version": [25, 5, 0],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def resources_pathSetting(newPath: str = ""):
|
||||||
|
if not os.path.isfile("./bgArrayLib/resourcesPath.rpposi") and newPath == "":
|
||||||
|
return [False, 1] # 1:没有路径文件
|
||||||
|
elif newPath != "": # not os.path.isfile("./bgArrayLib/resourcesPath.rpposi") and
|
||||||
|
path = newPath
|
||||||
|
print(path)
|
||||||
|
with open("./bgArrayLib/resourcesPath.rpposi", "w") as w:
|
||||||
|
w.write(path)
|
||||||
|
if "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" in os.listdir(path):
|
||||||
|
return [True, path, 1] # 1:都有
|
||||||
|
elif "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" not in os.listdir(path):
|
||||||
|
return [True, path, 2] # 2:有pack
|
||||||
|
elif "mcpack(国际版推荐)格式_25.0" not in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" in os.listdir(path):
|
||||||
|
return [True, path, 3] # 3:有zip
|
||||||
|
else:
|
||||||
|
return [False, 2] # 2:路径文件指示错误
|
||||||
|
if os.path.isfile("./bgArrayLib/resourcesPath.rpposi") and newPath == "":
|
||||||
|
with open("./bgArrayLib/resourcesPath.rpposi", "r") as f:
|
||||||
|
path = f.read()
|
||||||
|
if "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" in os.listdir(path):
|
||||||
|
return [True, path, 1] # 1:都有
|
||||||
|
elif "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" not in os.listdir(path):
|
||||||
|
return [True, path, 2] # 2:有pack
|
||||||
|
elif "mcpack(国际版推荐)格式_25.0" not in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" in os.listdir(path):
|
||||||
|
return [True, path, 3] # 3:有zip
|
||||||
|
else:
|
||||||
|
return [False, 2] # 2:路径文件指示错误
|
||||||
|
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def choose_resources():
|
||||||
|
global zipN
|
||||||
|
global mpN
|
||||||
|
back_list = []
|
||||||
|
try:
|
||||||
|
with open(r"1.pkl", "rb") as rb:
|
||||||
|
instrument = list(pickle.load(rb))
|
||||||
|
print(instrument)
|
||||||
|
except FileNotFoundError:
|
||||||
|
with open(r"./nmcsup/1.pkl", "rb") as rb:
|
||||||
|
instrument = list(pickle.load(rb))
|
||||||
|
print(instrument)
|
||||||
|
path = resources_pathSetting()
|
||||||
|
if path.__len__() == 2:
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
dataT = path[2]
|
||||||
|
pathT = path[1]
|
||||||
|
if dataT == 1:
|
||||||
|
if instrument[1] is True: # 是否存在打击乐器
|
||||||
|
index = zipN.get(-1, "")
|
||||||
|
percussion_instrument = str(pathT) + "\\zip格式_25.0\\" + index
|
||||||
|
# print(percussion_instrument)
|
||||||
|
back_list.append(percussion_instrument)
|
||||||
|
for i in instrument[0]:
|
||||||
|
ins_p = str(pathT) + "\\zip格式_25.0\\" + str(zipN.get(i))
|
||||||
|
# print(ins_p)
|
||||||
|
back_list.append(ins_p)
|
||||||
|
print(back_list)
|
||||||
|
return back_list
|
||||||
|
elif dataT == 2:
|
||||||
|
if instrument[1] is True:
|
||||||
|
index = mpN.get(-1, "")
|
||||||
|
percussion_instrument = (
|
||||||
|
str(pathT) + "\\mcpack(国际版推荐)格式_25.0\\" + index
|
||||||
|
)
|
||||||
|
# print(percussion_instrument)
|
||||||
|
back_list.append(percussion_instrument)
|
||||||
|
for i in instrument[0]:
|
||||||
|
ins_p = str(pathT) + "\\mcpack(国际版推荐)格式_25.0\\" + str(mpN.get(i))
|
||||||
|
# print(ins_p)
|
||||||
|
back_list.append(ins_p)
|
||||||
|
print(back_list)
|
||||||
|
return back_list
|
||||||
|
elif dataT == 3:
|
||||||
|
if instrument[1] is True:
|
||||||
|
index = zipN.get(-1, "")
|
||||||
|
percussion_instrument = str(pathT) + "\\zip格式_25.0\\" + index
|
||||||
|
# print(percussion_instrument)
|
||||||
|
back_list.append(percussion_instrument)
|
||||||
|
for i in instrument[0]:
|
||||||
|
ins_p = str(pathT) + "\\zip格式_25.0\\" + str(zipN.get(i))
|
||||||
|
# print(ins_p)
|
||||||
|
back_list.append(ins_p)
|
||||||
|
print(back_list)
|
||||||
|
return back_list
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def scatteredPack(path):
|
||||||
|
pack_list = choose_resources()
|
||||||
|
print(pack_list)
|
||||||
|
print(path)
|
||||||
|
# os.close("L:/0WorldMusicCreater-MFMS new edition")
|
||||||
|
# shutil.copy("L:\\shenyu\\音源的资源包\\羽音缭绕-midiout_25.0\\mcpack(国际版推荐)格式_25.0\\0.Acoustic_Grand_Piano_大钢琴.mcpack",
|
||||||
|
# "L:/0WorldMusicCreater-MFMS new edition")
|
||||||
|
for i in pack_list:
|
||||||
|
shutil.copy(i, path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# print(resources_pathSetting(r"L:\shenyu\音源的资源包\羽音缭绕-midiout_25.0"))
|
||||||
|
choose_resources()
|
||||||
@@ -18,19 +18,19 @@ Terms & Conditions: ./License.md
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
from Musicreater.plugin.addonpack import (
|
from Musicreater.old_plugin.addonpack import (
|
||||||
to_addon_pack_in_delay,
|
to_addon_pack_in_delay,
|
||||||
to_addon_pack_in_repeater,
|
to_addon_pack_in_repeater,
|
||||||
to_addon_pack_in_score,
|
to_addon_pack_in_score,
|
||||||
)
|
)
|
||||||
from Musicreater.plugin.mcstructfile import (
|
from Musicreater.old_plugin.mcstructfile import (
|
||||||
to_mcstructure_file_in_delay,
|
to_mcstructure_file_in_delay,
|
||||||
to_mcstructure_file_in_repeater,
|
to_mcstructure_file_in_repeater,
|
||||||
to_mcstructure_file_in_score,
|
to_mcstructure_file_in_score,
|
||||||
)
|
)
|
||||||
|
|
||||||
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
from Musicreater.old_plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||||
|
|
||||||
# 获取midi列表
|
# 获取midi列表
|
||||||
midi_path = input(f"请输入MIDI路径:")
|
midi_path = input(f"请输入MIDI路径:")
|
||||||
@@ -156,7 +156,7 @@ else:
|
|||||||
|
|
||||||
|
|
||||||
print(f"正在处理 {midi_path} :")
|
print(f"正在处理 {midi_path} :")
|
||||||
cvt_mid = Musicreater.MidiConvert.from_midi_file(
|
cvt_mid = old_init.MidiConvert.from_midi_file(
|
||||||
midi_path, old_exe_format=False, min_volume=prompts[0], play_speed=prompts[1]
|
midi_path, old_exe_format=False, min_volume=prompts[0], play_speed=prompts[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ print(
|
|||||||
cvt_method(
|
cvt_method(
|
||||||
cvt_mid,
|
cvt_mid,
|
||||||
out_path,
|
out_path,
|
||||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
|
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
|
||||||
*prompts[3:],
|
*prompts[3:],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -199,14 +199,14 @@ print(
|
|||||||
to_BDX_file_in_score(
|
to_BDX_file_in_score(
|
||||||
cvt_mid,
|
cvt_mid,
|
||||||
out_path,
|
out_path,
|
||||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||||
*prompts[3:],
|
*prompts[3:],
|
||||||
)
|
)
|
||||||
if playerFormat == 1
|
if playerFormat == 1
|
||||||
else to_BDX_file_in_delay(
|
else to_BDX_file_in_delay(
|
||||||
cvt_mid,
|
cvt_mid,
|
||||||
out_path,
|
out_path,
|
||||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||||
*prompts[3:],
|
*prompts[3:],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import Musicreater.experiment
|
import Musicreater.experiment
|
||||||
import Musicreater.plugin
|
import Musicreater.old_plugin
|
||||||
import Musicreater.plugin.mcstructfile
|
import Musicreater.old_plugin.mcstructfile
|
||||||
|
|
||||||
print(
|
print(
|
||||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||||
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
|
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
|
||||||
input("midi路径:"), old_exe_format=False
|
input("midi路径:"), old_exe_format=False
|
||||||
),
|
),
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
import Musicreater.plugin
|
import Musicreater.old_plugin
|
||||||
import Musicreater.plugin.mcstructfile
|
import Musicreater.old_plugin.mcstructfile
|
||||||
|
|
||||||
print(
|
print(
|
||||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
old_init.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||||
Musicreater.MidiConvert.from_midi_file(
|
old_init.MidiConvert.from_midi_file(
|
||||||
input("midi路径:"),
|
input("midi路径:"),
|
||||||
old_exe_format=False,
|
old_exe_format=False,
|
||||||
# note_table_replacement={"note.harp": "note.flute"},
|
# note_table_replacement={"note.harp": "note.flute"},
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
import Musicreater.plugin
|
import Musicreater.old_plugin
|
||||||
import Musicreater.plugin.websocket
|
import Musicreater.old_plugin.websocket
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
dire = input("midi目录:")
|
dire = input("midi目录:")
|
||||||
|
|
||||||
print(
|
print(
|
||||||
Musicreater.plugin.websocket.to_websocket_server(
|
old_init.old_plugin.websocket.to_websocket_server(
|
||||||
[
|
[
|
||||||
Musicreater.MidiConvert.from_midi_file(
|
old_init.MidiConvert.from_midi_file(
|
||||||
os.path.join(dire, names), old_exe_format=False
|
os.path.join(dire, names), old_exe_format=False
|
||||||
)
|
)
|
||||||
for names in os.listdir(
|
for names in os.listdir(
|
||||||
@@ -19,6 +19,6 @@ print(
|
|||||||
],
|
],
|
||||||
input("服务器地址:"),
|
input("服务器地址:"),
|
||||||
int(input("服务器端口:")),
|
int(input("服务器端口:")),
|
||||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE,
|
old_init.DEFAULT_PROGRESSBAR_STYLE,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
1
old-things/fcwslib/LICENSE
Normal file
1
old-things/fcwslib/LICENSE
Normal file
@@ -0,0 +1 @@
|
|||||||
|
SEE: https://mingfengpigeon.mit-license.org/
|
||||||
5
old-things/fcwslib/__init__.py
Normal file
5
old-things/fcwslib/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
__all__ = ['Server', 'Plugin', 'build_header']
|
||||||
|
__version__ = '3.0.1'
|
||||||
|
__author__ = ['mingfengpigeon <mingfengpigeon@gmail.com>',"Eilles Wan <EillesWan@outlook.com>"]
|
||||||
|
|
||||||
|
from .server import Server, Plugin, build_header
|
||||||
142
old-things/fcwslib/server.py
Normal file
142
old-things/fcwslib/server.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import asyncio
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import websockets
|
||||||
|
|
||||||
|
|
||||||
|
class Server:
|
||||||
|
sent_commands = {}
|
||||||
|
subscribed_events = {}
|
||||||
|
_plugins = []
|
||||||
|
_connections = []
|
||||||
|
|
||||||
|
def __init__(self, server='0.0.0.0', port=8000, debug_mode=False):
|
||||||
|
self._server = server
|
||||||
|
self._port = port
|
||||||
|
self._debug_mode = debug_mode
|
||||||
|
|
||||||
|
def handler(self):
|
||||||
|
return copy.deepcopy(self._plugins)
|
||||||
|
|
||||||
|
def add_plugin(self, plugin):
|
||||||
|
if self._plugins:
|
||||||
|
for connection in self._connections:
|
||||||
|
plugin_ = plugin()
|
||||||
|
asyncio.create_task(plugin_.on_connect())
|
||||||
|
connection.append(plugin_)
|
||||||
|
self._plugins.append(plugin)
|
||||||
|
|
||||||
|
def remove_plugin(self, plugin):
|
||||||
|
if self._connections:
|
||||||
|
for connection in self._connections:
|
||||||
|
for plugin_ in connection.plugins:
|
||||||
|
if isinstance(plugin_, plugin):
|
||||||
|
plugin_.remove(plugin_)
|
||||||
|
break
|
||||||
|
self._plugins.remove(plugin)
|
||||||
|
|
||||||
|
async def run_forever(self):
|
||||||
|
self.running = True
|
||||||
|
async with websockets.serve(self._on_connect, self._server, self._port):
|
||||||
|
await asyncio.Future()
|
||||||
|
|
||||||
|
async def _on_connect(self, websocket, path):
|
||||||
|
plugins = []
|
||||||
|
self._connections.append({
|
||||||
|
"websocket": websocket,
|
||||||
|
"path": path,
|
||||||
|
"plugins": plugins,
|
||||||
|
})
|
||||||
|
for plugin in self._plugins:
|
||||||
|
plugins.append(plugin(websocket, path, self, self._debug_mode))
|
||||||
|
for plugin in plugins:
|
||||||
|
asyncio.create_task(plugin.on_connect())
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
response = json.loads(await websocket.recv())
|
||||||
|
except (websockets.exceptions.ConnectionClosedOK, websockets.exceptions.ConnectionClosedError):
|
||||||
|
tasks = []
|
||||||
|
for plugin in plugins:
|
||||||
|
tasks.append(plugin.on_disconnect())
|
||||||
|
for task in tasks:
|
||||||
|
await task
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
message_purpose = response['header']['messagePurpose']
|
||||||
|
if message_purpose == 'commandResponse':
|
||||||
|
request_id = response['header']['requestId']
|
||||||
|
if request_id in self.sent_commands:
|
||||||
|
asyncio.create_task(self.sent_commands[request_id](response))
|
||||||
|
del self.sent_commands[request_id]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
event_name = response['header']['eventName']
|
||||||
|
asyncio.create_task(self.subscribed_events[event_name](response))
|
||||||
|
except KeyError:
|
||||||
|
print("ERROR EVENT NAME:\n{}".format(response))
|
||||||
|
|
||||||
|
async def disconnect(self, websocket: websockets.WebSocketServerProtocol):
|
||||||
|
self.running = False
|
||||||
|
await websocket.close_connection()
|
||||||
|
for number in range(len(self._connections) - 1):
|
||||||
|
connection = self._connections[number]
|
||||||
|
if connection['websocket'] == websocket:
|
||||||
|
del self._connections[number]
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin:
|
||||||
|
def __init__(self, websocket, path, server, debug_mode=False):
|
||||||
|
self._websocket = websocket
|
||||||
|
self._path = path
|
||||||
|
self._server = server
|
||||||
|
self._debug_mode = debug_mode
|
||||||
|
|
||||||
|
async def on_connect(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_disconnect(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_receive(self, response):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def send_command(self, command, callback=None):
|
||||||
|
request = {
|
||||||
|
'body': {'commandLine': command},
|
||||||
|
'header': build_header('commandRequest')
|
||||||
|
}
|
||||||
|
if callback:
|
||||||
|
self._server.sent_commands[request['header']['requestId']] = callback
|
||||||
|
await self._websocket.send(json.dumps(request, **{'indent': 4} if self._debug_mode else {}))
|
||||||
|
|
||||||
|
async def subscribe(self, event_name, callback):
|
||||||
|
request = {
|
||||||
|
'body': {'eventName': event_name},
|
||||||
|
'header': build_header('subscribe')
|
||||||
|
}
|
||||||
|
self._server.subscribed_events[event_name] = callback
|
||||||
|
await self._websocket.send(json.dumps(request, **{'indent': 4} if self._debug_mode else {}))
|
||||||
|
|
||||||
|
async def unsubscribe(self, event_name):
|
||||||
|
request = {
|
||||||
|
'body': {'eventName': event_name},
|
||||||
|
'header': build_header('unsubscribe')
|
||||||
|
}
|
||||||
|
del self._server.subscribed_events[event_name]
|
||||||
|
await self._websocket.send(json.dumps(request, **{'indent': 4} if self._debug_mode else {}))
|
||||||
|
|
||||||
|
async def disconnect(self):
|
||||||
|
await self._server.disconnect(self._websocket)
|
||||||
|
|
||||||
|
|
||||||
|
def build_header(message_purpose, request_id=None):
|
||||||
|
if not request_id:
|
||||||
|
request_id = str(uuid.uuid4())
|
||||||
|
return {
|
||||||
|
'requestId': request_id,
|
||||||
|
'messagePurpose': message_purpose,
|
||||||
|
'version': '1',
|
||||||
|
'messageType': 'commandRequest',
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import shutil
|
|||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
import Musicreater.experiment
|
import Musicreater.experiment
|
||||||
from Musicreater.plugin.archive import compress_zipfile
|
from Musicreater.old_plugin.archive import compress_zipfile
|
||||||
from Musicreater.utils import guess_deviation, is_in_diapason
|
from Musicreater.utils import guess_deviation, is_in_diapason
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
@@ -123,7 +123,7 @@ msc_cvt = Musicreater.experiment.FutureMidiConvertJavaE.from_midi_file(
|
|||||||
input("midi路径:"),
|
input("midi路径:"),
|
||||||
play_speed=float(input("播放速度:")),
|
play_speed=float(input("播放速度:")),
|
||||||
old_exe_format=True,
|
old_exe_format=True,
|
||||||
note_table_replacement=Musicreater.MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
|
note_table_replacement=Musicreater.old_init.MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
|
||||||
# pitched_note_table=Musicreater.MM_NBS_PITCHED_INSTRUMENT_TABLE,
|
# pitched_note_table=Musicreater.MM_NBS_PITCHED_INSTRUMENT_TABLE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
from rich.pretty import pprint
|
from rich.pretty import pprint
|
||||||
|
|
||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
from Musicreater.utils import (
|
from Musicreater.utils import (
|
||||||
load_decode_fsq_flush_release,
|
load_decode_fsq_flush_release,
|
||||||
load_decode_musicsequence_metainfo,
|
load_decode_musicsequence_metainfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
msc_seq = Musicreater.MusicSequence.from_mido(
|
msc_seq = old_init.MusicSequence.from_mido(
|
||||||
Musicreater.mido.MidiFile(
|
old_init.mido.MidiFile(
|
||||||
"./resources/测试片段.mid",
|
"./resources/测试片段.mid",
|
||||||
),
|
),
|
||||||
"TEST-测试片段",
|
"TEST-测试片段",
|
||||||
@@ -20,7 +20,7 @@ with open("test.fsq", "wb") as f:
|
|||||||
f.write(fsq_bytes := msc_seq.encode_dump(flowing_codec_support=True))
|
f.write(fsq_bytes := msc_seq.encode_dump(flowing_codec_support=True))
|
||||||
|
|
||||||
with open("test.fsq", "rb") as f:
|
with open("test.fsq", "rb") as f:
|
||||||
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read(), verify=True)
|
msc_seq_r = old_init.MusicSequence.load_decode(f.read(), verify=True)
|
||||||
|
|
||||||
pprint("FSQ 传入类成功:")
|
pprint("FSQ 传入类成功:")
|
||||||
pprint(msc_seq_r)
|
pprint(msc_seq_r)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import Musicreater.experiment
|
import Musicreater.experiment
|
||||||
import Musicreater.plugin
|
import Musicreater.old_plugin
|
||||||
import Musicreater.plugin.mcstructfile
|
import Musicreater.old_plugin.mcstructfile
|
||||||
|
|
||||||
msct = Musicreater.experiment.FutureMidiConvertKamiRES.from_midi_file(
|
msct = Musicreater.experiment.FutureMidiConvertKamiRES.from_midi_file(
|
||||||
input("midi路径:"), old_exe_format=False
|
input("midi路径:"), old_exe_format=False
|
||||||
@@ -24,7 +24,7 @@ for name in sorted(
|
|||||||
|
|
||||||
print(
|
print(
|
||||||
"\n输出:",
|
"\n输出:",
|
||||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||||
msct,
|
msct,
|
||||||
opt,
|
opt,
|
||||||
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import Musicreater.experiment
|
import Musicreater.experiment
|
||||||
import Musicreater.plugin
|
import Musicreater.old_plugin
|
||||||
import Musicreater.plugin.mcstructfile
|
import Musicreater.old_plugin.mcstructfile
|
||||||
|
|
||||||
msct = Musicreater.experiment.FutureMidiConvertLyricSupport.from_midi_file(
|
msct = Musicreater.experiment.FutureMidiConvertLyricSupport.from_midi_file(
|
||||||
input("midi路径:"), old_exe_format=False
|
input("midi路径:"), old_exe_format=False
|
||||||
@@ -24,7 +24,7 @@ opt = input("输出路径:")
|
|||||||
|
|
||||||
print(
|
print(
|
||||||
"\n输出:",
|
"\n输出:",
|
||||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||||
msct,
|
msct,
|
||||||
opt,
|
opt,
|
||||||
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
from rich.pretty import pprint
|
from rich.pretty import pprint
|
||||||
|
|
||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
from Musicreater.utils import (
|
from Musicreater.utils import (
|
||||||
load_decode_msq_flush_release,
|
load_decode_msq_flush_release,
|
||||||
load_decode_musicsequence_metainfo,
|
load_decode_musicsequence_metainfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
msc_seq = Musicreater.MusicSequence.from_mido(
|
msc_seq = old_init.MusicSequence.from_mido(
|
||||||
Musicreater.mido.MidiFile(
|
old_init.mido.MidiFile(
|
||||||
"./resources/测试片段.mid",
|
"./resources/测试片段.mid",
|
||||||
),
|
),
|
||||||
"TEST-测试片段",
|
"TEST-测试片段",
|
||||||
@@ -20,7 +20,7 @@ with open("test.msq", "wb") as f:
|
|||||||
f.write(msq_bytes := msc_seq.encode_dump())
|
f.write(msq_bytes := msc_seq.encode_dump())
|
||||||
|
|
||||||
with open("test.msq", "rb") as f:
|
with open("test.msq", "rb") as f:
|
||||||
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read())
|
msc_seq_r = old_init.MusicSequence.load_decode(f.read())
|
||||||
|
|
||||||
pprint("常规 MSQ 读取成功:")
|
pprint("常规 MSQ 读取成功:")
|
||||||
pprint(msc_seq_r)
|
pprint(msc_seq_r)
|
||||||
@@ -3,8 +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'",
|
||||||
"xxhash >= 3",
|
"tomli-w >= 1.0.0, < 2.0",
|
||||||
|
"xxhash >= 3.0, < 4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
authors = [
|
authors = [
|
||||||
@@ -44,13 +45,25 @@
|
|||||||
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
full = [
|
midi = [
|
||||||
|
"mido >= 1.3, < 2.0",
|
||||||
|
]
|
||||||
|
structure = [
|
||||||
|
"numpy",
|
||||||
"TrimMCStruct <= 0.0.5.9",
|
"TrimMCStruct <= 0.0.5.9",
|
||||||
"brotli >= 1.0.0",
|
"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",
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
# 注意,这里是作者署名文件,文件格式开头为单子启
|
|
||||||
# 紧跟其后,不加空格留下常用名,常用名即常用网名
|
|
||||||
# 而在其后是各个语言下的名字。用 井字符 开头表示
|
|
||||||
# 注释,请注意,注释符号必须在一行之首否则无作用
|
|
||||||
# 每进行一次分段表示一个新的开发者,换行表示一个
|
|
||||||
# 新的语言。请全体开发者就此署名,谢谢!
|
|
||||||
启金羿
|
|
||||||
zh-CN 金羿
|
|
||||||
zh-TW 金羿
|
|
||||||
zh-ME 金羿羿喵
|
|
||||||
zh-HK 金 羿
|
|
||||||
en-GB Eilles
|
|
||||||
en-US EillesWan
|
|
||||||
|
|
||||||
启诸葛亮与八卦阵
|
|
||||||
zh-CN 诸葛亮与八卦阵
|
|
||||||
zh-TW 諸葛亮與八卦陣
|
|
||||||
zh-ME 诸葛八卦喵
|
|
||||||
zh-HK 諸葛亮與八卦陣
|
|
||||||
en-GB Bagua Array
|
|
||||||
en-US bgArray
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.6 MiB After Width: | Height: | Size: 130 KiB |
@@ -1,47 +0,0 @@
|
|||||||
> 是谁把科技的领域布满政治的火药
|
|
||||||
>
|
|
||||||
> 是谁把纯净的蓝天染上暗淡的沉灰
|
|
||||||
>
|
|
||||||
> 中国人民无不热爱自己伟大的祖国
|
|
||||||
>
|
|
||||||
> 我们不会忘记屈辱历史留下的惨痛
|
|
||||||
>
|
|
||||||
> 我们希望世界和平
|
|
||||||
>
|
|
||||||
> 我们希望获得世界的尊重
|
|
||||||
>
|
|
||||||
> 愿世上再也没有战争
|
|
||||||
>
|
|
||||||
> 无论是热还是冷
|
|
||||||
>
|
|
||||||
> 无论是经济还是政治
|
|
||||||
>
|
|
||||||
> 让美妙的和平的优雅的音乐响彻世界
|
|
||||||
>
|
|
||||||
> ——金羿
|
|
||||||
> 2022 5 7
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> Who has dropped political gunpowder into the technology
|
|
||||||
>
|
|
||||||
> Who has dyed clear blue sky into the dark grey
|
|
||||||
>
|
|
||||||
> All Chinese people love our great homeland
|
|
||||||
>
|
|
||||||
> We *WILL* remember the remain pain of the humiliating history
|
|
||||||
>
|
|
||||||
> We love the whole world but in peace
|
|
||||||
>
|
|
||||||
> We love everyone but under respect
|
|
||||||
>
|
|
||||||
> It is to be hoped that the war ends forever
|
|
||||||
>
|
|
||||||
> Whatever it is cold or hot
|
|
||||||
>
|
|
||||||
> Whatever it is economical or political
|
|
||||||
>
|
|
||||||
> Just let the wonderful music of peace surround the world
|
|
||||||
>
|
|
||||||
> ---- Eilles
|
|
||||||
> 7/5 2022
|
|
||||||
21
resources/test/genexpr_vs_yieldfrom.py
Normal file
21
resources/test/genexpr_vs_yieldfrom.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
# 模拟两种写法
|
||||||
|
def method_A(self, start, end):
|
||||||
|
yield from (f"{track}.get_range(start, end)" for track in self)
|
||||||
|
|
||||||
|
def method_B(self, start, end):
|
||||||
|
return (f"{track}.get_range(start, end)" for track in self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
tracks = ["A", "B"]
|
||||||
|
|
||||||
|
gen_a = method_A(tracks, 0, 10)
|
||||||
|
print(list(gen_a))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
gen_b = method_B(tracks, 0, 10)
|
||||||
|
print(list(gen_b))
|
||||||
|
|
||||||
|
# they are the same output
|
||||||
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -11,4 +11,4 @@
|
|||||||
不得用于商业用途
|
不得用于商业用途
|
||||||
若 音·创 库被用于商业用途,应当将其剔除
|
若 音·创 库被用于商业用途,应当将其剔除
|
||||||
|
|
||||||
版权所有 © 2025 诸葛亮与八卦阵
|
版权所有 © 2026 玉衡Alioth
|
||||||
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(),
|
||||||
|
),
|
||||||
|
)
|
||||||
39
test_read.py
Normal file
39
test_read.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 一个简单的项目实践测试
|
||||||
|
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")
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.midi_read import MidiImportConfig
|
||||||
|
|
||||||
|
print("当前支持的导入格式:", _global_plugin_registry.supported_input_formats())
|
||||||
|
print("当前支持的导出格式:", _global_plugin_registry.supported_output_formats())
|
||||||
|
|
||||||
|
print(msct := MusiCreater.import_music(Path("./resources/测试片段.mid")))
|
||||||
|
|
||||||
|
print(msct.music)
|
||||||
|
|
||||||
|
# 如果要直接访问插件里面的函数:
|
||||||
|
# 为了确保类型安全,以下方法不建议使用,因为这本质上是越过了 MusiCreater 类而直接执行插件的函数
|
||||||
|
print(t := msct.midi_to_music_plugin.load(Path("./resources/测试片段.mid"), None)) # type: ignore
|
||||||
|
# 我们建议用这种方式来代替
|
||||||
|
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(msct._plugin_cache)
|
||||||
122
uv.lock
generated
122
uv.lock
generated
@@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 1
|
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,7 +577,9 @@ wheels = [
|
|||||||
name = "musicreater"
|
name = "musicreater"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "mido" },
|
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||||
|
{ 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 = "xxhash" },
|
{ name = "xxhash" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -585,6 +587,10 @@ 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" },
|
||||||
@@ -592,23 +598,46 @@ dev = [
|
|||||||
]
|
]
|
||||||
full = [
|
full = [
|
||||||
{ name = "brotli" },
|
{ 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 = "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" },
|
{ name = "trimmcstruct" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[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 == 'structure'" },
|
||||||
{ name = "pyinstaller", marker = "extra == 'dev'" },
|
{ name = "pyinstaller", marker = "extra == 'dev'" },
|
||||||
{ name = "rich", marker = "extra == 'dev'" },
|
{ name = "rich", marker = "extra == 'dev'" },
|
||||||
|
{ name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.4.0,<3.0" },
|
||||||
|
{ 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"
|
||||||
@@ -1041,6 +1070,85 @@ wheels = [
|
|||||||
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/58/29/93c53c098d301132196c3238c312825324740851d77a8500a2462c0fd888/setuptools-80.8.0-py3-none-any.whl", hash = "sha256:95a60484590d24103af13b686121328cc2736bee85de8936383111e421b9edc0" },
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/58/29/93c53c098d301132196c3238c312825324740851d77a8500a2462c0fd888/setuptools-80.8.0-py3-none-any.whl", hash = "sha256:95a60484590d24103af13b686121328cc2736bee85de8936383111e421b9edc0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }
|
||||||
|
sdist = { url = "https://mirror.nju.edu.cn/pypi/web/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4" },
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli-w"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version < '3.9'",
|
||||||
|
]
|
||||||
|
sdist = { url = "https://mirror.nju.edu.cn/pypi/web/packages/49/05/6bf21838623186b91aedbda06248ad18f03487dc56fbc20e4db384abde6c/tomli_w-1.0.0.tar.gz", hash = "sha256:f463434305e0336248cac9c2dc8076b707d8a12d019dd349f5c1e382dd1ae1b9" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/bb/01/1da9c66ecb20f31ed5aa5316a957e0b1a5e786a0d9689616ece4ceaf1321/tomli_w-1.0.0-py3-none-any.whl", hash = "sha256:9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli-w"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.10'",
|
||||||
|
"python_full_version == '3.9.*'",
|
||||||
|
]
|
||||||
|
sdist = { url = "https://mirror.nju.edu.cn/pypi/web/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://mirror.nju.edu.cn/pypi/web/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "trimmcstruct"
|
name = "trimmcstruct"
|
||||||
version = "0.0.5.9"
|
version = "0.0.5.9"
|
||||||
|
|||||||
Reference in New Issue
Block a user