临时上传,仍在开发过程中

This commit is contained in:
2026-02-02 01:33:47 +08:00
parent 0de959c396
commit 841f6e53c6
20 changed files with 1332 additions and 965 deletions

View File

@@ -1,19 +1,24 @@
# -*- coding: utf-8 -*-
"""一个简单的我的世界音频转换库
音·创 (Musicreater)
"""
音·创
是一款免费开源的《我的世界》数字音频支持库。
Musicreater(音·创)
A free open source library used for dealing with **Minecraft** digital musics.
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
Musicreater (音·创)
A free and open-source library for handling with **Minecraft** digital music.
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
The Licensor of Musicreater("this project") is Eilles, bgArray.
版权所有 © 2026 睿乐组织
Copyright © 2026 TriM-Organization
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
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,12 @@ The Licensor of Musicreater("this project") is Eilles, bgArray.
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__version__ = "2.4.2.3"
__vername__ = "音符附加信息升级"
__version__ = "3.0.0-alpha"
__author__ = (
("金羿", "Eilles"),
("诸葛亮与八卦阵", "bgArray"),
("玉衡Alioth", "YuhengAlioth"),
("鱼旧梦", "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
from .subclass import (
MineNote,
MineCommand,
SingleNoteBox,
ProgressBarStyle,
mctick2timestr,
DEFAULT_PROGRESSBAR_STYLE,
)
from .utils import (
# 兼容性函数
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 (
# 字典键
MIDI_PROGRAM,
MIDI_PAN,
MIDI_VOLUME,
# 默认值
MIDI_DEFAULT_PROGRAM_VALUE,
MIDI_DEFAULT_VOLUME_VALUE,
# 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,
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,
)

412
Musicreater/_plugin_abc.py Normal file
View File

@@ -0,0 +1,412 @@
# -*- coding: utf-8 -*-
"""
存储 音·创 v3 的插件基类,提供抽象接口以供实际插件使用
"""
"""
版权所有 © 2025 金羿
Copyright © 2025 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
from enum import Enum
from pathlib import Path
from typing import (
Dict,
Any,
Optional,
List,
Tuple,
Union,
Sequence,
BinaryIO,
Generator,
Iterator,
Set,
)
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",
# ]
@dataclass
class PluginConfig(ABC):
"""插件配置基类"""
def to_dict(self) -> Dict[str, Any]:
"""字典化配置文件"""
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
@classmethod
def from_dict(cls, 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:
"""保存配置到文件"""
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(e)
@classmethod
def load_from_file(cls, file_path: Path) -> "PluginConfig":
"""从文件加载配置"""
try:
with file_path.open("rb") as f:
return cls.from_dict(tomllib.load(f))
except Exception as e:
raise PluginConfigLoadError(e)
class PluginType(str, Enum):
"""插件类型枚举"""
FUNCTION_IMPORT = "import_data"
FUNCTION_EXPORT = "export_data"
FUNCTION_OPERATE = "data_operate"
SERVICE = "service"
LIBRARY = "library"
@dataclass
class PluginMetaInformation(ABC):
"""插件元信息"""
name: str
"""插件名称,应为惟一之名"""
author: str
"""插件作者"""
description: str
"""插件简介"""
version: Tuple[int, ...]
"""插件版本号"""
type: PluginType
"""插件类型"""
license: str = "MIT License"
"""插件发布时采用的许可协议"""
dependencies: Sequence[str] = []
"""插件是否对其他插件存在依赖"""
class TopBasePlugin(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:
raise PluginMetainfoNotFoundError(
"类`{cls_name}`必须定义一个`metainfo`属性。".format(
cls_name=cls.__name__
)
)
class TopInOutBasePlugin(TopBasePlugin, 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:
"""判断是否可处理某个文件"""
return file_path.suffix.upper().endswith(self.supported_formats)
def can_handle_format(self, format_name: str) -> bool:
"""判断是否可处理某个格式"""
return format_name.upper().endswith(self.supported_formats)
class MusicInputPlugin(TopInOutBasePlugin, ABC):
"""导入用插件抽象基类-完整曲目"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_IMPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_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":
"""从字节流加载数据到完整曲目"""
pass
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
"""从文件加载数据到完整曲目"""
with file_path.open("rb") as f:
return self.loadbytes(f, config)
class TrackInputPlugin(TopInOutBasePlugin, ABC):
"""导入用插件抽象基类-单个音轨"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_IMPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_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":
"""从字节流加载音符数据到单个音轨"""
pass
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack":
"""从文件加载音符数据到单个音轨"""
with file_path.open("rb") as f:
return self.loadbytes(f, config)
class MusicOperatePlugin(TopBasePlugin, ABC):
"""音乐处理用插件抽象基类-完整曲目"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_OPERATE:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_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":
"""处理完整曲目的数据"""
pass
class TrackOperatePlugin(TopBasePlugin, ABC):
"""音乐处理用插件抽象基类-单个音轨"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_OPERATE:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_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":
"""处理单个音轨的音符数据"""
pass
class MusicOutputPlugin(TopInOutBasePlugin, ABC):
"""导出用插件的抽象基类-完整曲目"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_EXPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def dumpbytes(
self, data: "SingleMusic", config: Optional[PluginConfig]
) -> BinaryIO:
"""将完整曲目导出为对应格式的字节流"""
pass
@abstractmethod
def dump(
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
):
"""将完整曲目导出为对应格式的文件"""
pass
class TrackOutputPlugin(TopInOutBasePlugin, ABC):
"""导出用插件的抽象基类-单个音轨"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_EXPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def dumpbytes(
self, data: "SingleTrack", config: Optional[PluginConfig]
) -> BinaryIO:
"""将单个音轨导出为对应格式的字节流"""
pass
@abstractmethod
def dump(
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
):
"""将单个音轨导出为对应格式的文件"""
pass
class ServicePlugin(TopBasePlugin, ABC):
"""服务插件抽象基类"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.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], *args) -> None:
"""服务插件的运行逻辑"""
pass
class LibraryPlugin(TopBasePlugin, ABC):
"""插件依赖库的抽象基类"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.LIBRARY:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
# 怎么?
# 插件的彼此依赖就不需要什么调用了吧

View File

@@ -44,6 +44,9 @@ MIDI_PAN = "pan"
"""Midi通道立体声场偏移"""
# Midi用对照表
MIDI_DEFAULT_VOLUME_VALUE: int = (

View File

@@ -16,7 +16,13 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
# “
# 把代码 洒落在这里
# 和音符 留下的沙砾
# 一点一点爬进你类定义的缝隙
# ” —— 乐曲访问 by resnah
import heapq
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf, ceil
from dataclasses import dataclass
from typing import (
@@ -33,12 +39,16 @@ from typing import (
Iterable,
Iterator,
Literal,
Hashable,
TypeVar,
)
from enum import Enum
from .exceptions import SingleNoteDecodeError, ParameterTypeError
from .exceptions import SingleNoteDecodeError, ParameterTypeError, ParameterValueError
from .paramcurve import ParamCurve
T = TypeVar("T")
class SoundAtmos:
"""声源方位类"""
@@ -411,6 +421,9 @@ class SingleTrack(List[SingleNote]):
track_name: str
"""轨道之名称"""
is_enabled: bool
"""该音轨是否启用"""
track_instrument: str
"""乐器ID"""
@@ -440,12 +453,16 @@ class SingleTrack(List[SingleNote]):
precise_time: bool = True,
percussion: bool = False,
sound_direction: SoundAtmos = SoundAtmos(),
enabled: bool = True,
extra_information: Dict[str, Any] = {},
*args: SingleNote,
):
self.track_name = name
"""音轨名称"""
self.is_enabled = enabled
"""音轨启用情况"""
self.track_instrument = instrument
"""乐器ID"""
@@ -494,27 +511,40 @@ class SingleTrack(List[SingleNote]):
)
)
super().append(item)
super().sort()
super().sort() # =========================== TODO 需要优化
def update(self, items: Iterable[SingleNote]):
"""
拼接两个音轨
"""
super().extend(items)
super().sort()
super().sort() # =========================== TODO 需要优化
def get(self, time: int) -> Iterator[SingleNote]:
def get(self, time: int) -> Generator[SingleNote, None, None]:
"""通过开始时间来获取音符"""
return filter(lambda x: x.start_time == time, self)
return (x for x in self if x.start_time == time)
def get_range(
def get_notes(
self, start_time: float, end_time: float = inf
) -> Iterator[SingleNote]:
) -> Generator[SingleNote, None, None]:
"""通过开始时间和结束时间来获取音符"""
return filter(
lambda x: (x.start_time >= start_time) and (x.start_time <= end_time), self
if end_time < start_time:
raise ParameterValueError(
"获取音符的时间范围有误,终止时间`{}`早于起始时间`{}`".format(
end_time, start_time
)
)
elif start_time < 0 or end_time < 0:
raise ParameterValueError(
"获取音符的时间范围有误,终止时间`{}`和起始时间`{}`皆不可为负数".format(
end_time, start_time
)
)
return (
x
for x in self
if (x.start_time >= start_time) and (x.start_time <= end_time)
)
def get_minenotes(
@@ -522,7 +552,7 @@ class SingleTrack(List[SingleNote]):
) -> Generator[MineNote, Any, None]:
"""获取能够用以在我的世界播放的音符数据类"""
for _note in self.get_range(range_start_time, range_end_time):
for _note in self.get_notes(range_start_time, range_end_time):
yield MineNote.from_single_note(
note=_note,
note_instrument=self.track_instrument,
@@ -626,9 +656,107 @@ class SingleMusic(List[SingleTrack]):
return len(self)
@property
def music_tracks(self) -> List[SingleTrack]:
"""音轨列表"""
return self
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 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)
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)
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],
sort_key=lambda x: x.start_time,
)
def get_minenotes(
self, start_time: float, end_time: float = inf
) -> Generator[MineNote, Any, None]:
"""获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序"""
if self.track_amount == 0:
return
yield from self.yield_from_tracks(
[track.get_minenotes(start_time, end_time) for track in self],
sort_key=lambda x: x.start_tick,
)
def set_info(self, key: Union[str, Sequence[str]], value: Any):
"""设置附加信息"""

View File

@@ -5,8 +5,8 @@
"""
"""
版权所有 © 2025 金羿 & 玉衡
Copyright © 2025 Eilles & Alioth
版权所有 © 2025 金羿 & 玉衡Alioth
Copyright © 2025 Eilles & YuhengAlioth
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -16,6 +16,10 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 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 MusicreaterBaseException(Exception):

80
Musicreater/main.py Normal file
View File

@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
"""
音·创
是一款免费开源的《我的世界》数字音频支持库。
Musicreater (音·创)
A free and open-source library for handling with **Minecraft** digital music.
版权所有 © 2026 睿乐组织
Copyright © 2026 TriM-Organization
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
The Licensor of Musicreater("this project") is Eilles, YuhengAlioth
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
任何人皆可从以下地址获得本协议副本:
https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,
不予提供任何形式的担保、任何明示、任何暗示或类似承诺。
也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
详细的准许和限制条款请见原协议文本。
"""
# 音·创 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
# BUG退散BUG退散
# 异常与错误作乱之时
# 二六字组!万国码合!二六字组!万国码合!
# 赶快呼叫 程序员Let's Go
# BUG退散BUG退散
# 異常、誤りが、困った時は
# パラメータ メソッド!パラメータ メソッド!
# 助けてもらおう、開発者!レッツゴー!
# Bug retreat! Bug retreat!
# Exceptions and errors are causing chaos
# Words combine! Codes unite!
# Hurry to call the programmer! Let's Go!
from typing import Dict, Generator, List, Optional, Tuple, Union
from pathlib import Path
from .data import SingleMusic, SingleTrack
from ._plugin_abc import TopBasePlugin
from .plugins import __global_plugin_registry, PluginRegistry
class MusiCreater:
"""
音·创 v3 主要控制类
“创建者”一词的英文应该是“Creator”
"""
__plugin_registry: PluginRegistry
"""插件注册表实例"""
_plugin_cache: Dict[str, TopBasePlugin]
"""插件缓存字典,插件名为键、插件实例为值"""
music: SingleMusic
"""当前曲目实例"""
def __init__(self, whole_music: SingleMusic) -> None:
global __global_plugin_registry
self.__plugin_registry = __global_plugin_registry
self._plugin_cache = {}
self.music = whole_music
def import_music(self, file_path: Path, plugin_name: Optional[str] = None) -> SingleMusic:

144
Musicreater/old_init.py Normal file
View File

@@ -0,0 +1,144 @@
# -*- 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
from .subclass import (
MineNote,
MineCommand,
SingleNoteBox,
ProgressBarStyle,
mctick2timestr,
DEFAULT_PROGRESSBAR_STYLE,
)
from .utils import (
# 兼容性函数
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 (
# 字典键
MIDI_PROGRAM,
MIDI_PAN,
MIDI_VOLUME,
# 默认值
MIDI_DEFAULT_PROGRAM_VALUE,
MIDI_DEFAULT_VOLUME_VALUE,
# 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,
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,
)

View File

@@ -1,783 +0,0 @@
# -*- coding: utf-8 -*-
"""
存储 音·创 v3 的插件接口与管理相关,提供抽象基类以供其他插件使用
"""
"""
版权所有 © 2025 金羿
Copyright © 2025 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, ABCMeta
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import (
Dict,
Any,
Optional,
List,
Tuple,
Union,
Sequence,
BinaryIO,
Generator,
Iterator,
Set,
)
from itertools import chain
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
__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",
# 全局插件注册表
"plugin_registry",
]
@dataclass
class PluginConfig(ABC):
"""插件配置基类"""
def to_dict(self) -> Dict[str, Any]:
"""字典化配置文件"""
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
@classmethod
def from_dict(cls, 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:
"""保存配置到文件"""
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(e)
@classmethod
def load_from_file(cls, file_path: Path) -> "PluginConfig":
"""从文件加载配置"""
try:
with file_path.open("rb") as f:
return cls.from_dict(tomllib.load(f))
except Exception as e:
raise PluginConfigLoadError(e)
class PluginType(str, Enum):
"""插件类型枚举"""
FUNCTION_IMPORT = "import_data"
FUNCTION_EXPORT = "export_data"
FUNCTION_OPERATE = "data_operate"
SERVICE = "service"
LIBRARY = "library"
@dataclass
class PluginMetaInformation(ABC):
"""插件元信息"""
name: str
"""插件名称,应为惟一之名"""
author: str
"""插件作者"""
description: str
"""插件简介"""
version: Tuple[int, ...]
"""插件版本号"""
type: PluginType
"""插件类型"""
license: str = "MIT License"
"""插件发布时采用的许可协议"""
dependencies: Sequence[str] = []
"""插件是否对其他插件存在依赖"""
class TopBasePlugin(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:
raise PluginMetainfoNotFoundError(
"类`{cls_name}`必须定义一个`metainfo`属性。".format(
cls_name=cls.__name__
)
)
class TopInOutBasePlugin(TopBasePlugin, 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:
"""判断是否可处理某个文件"""
return file_path.suffix.upper().endswith(self.supported_formats)
def can_handle_format(self, format_name: str) -> bool:
"""判断是否可处理某个格式"""
return format_name.upper().endswith(self.supported_formats)
class MusicInputPlugin(TopInOutBasePlugin, ABC):
"""导入用插件抽象基类-完整曲目"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_IMPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_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":
"""从字节流加载数据到完整曲目"""
pass
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
"""从文件加载数据到完整曲目"""
with file_path.open("rb") as f:
return self.loadbytes(f, config)
class TrackInputPlugin(TopInOutBasePlugin, ABC):
"""导入用插件抽象基类-单个音轨"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_IMPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_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":
"""从字节流加载音符数据到单个音轨"""
pass
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack":
"""从文件加载音符数据到单个音轨"""
with file_path.open("rb") as f:
return self.loadbytes(f, config)
class MusicOperatePlugin(TopBasePlugin, ABC):
"""音乐处理用插件抽象基类-完整曲目"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_OPERATE:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_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":
"""处理完整曲目的数据"""
pass
class TrackOperatePlugin(TopBasePlugin, ABC):
"""音乐处理用插件抽象基类-单个音轨"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_OPERATE:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_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":
"""处理单个音轨的音符数据"""
pass
class MusicOutputPlugin(TopInOutBasePlugin, ABC):
"""导出用插件的抽象基类-完整曲目"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_EXPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def dumpbytes(
self, data: "SingleMusic", config: Optional[PluginConfig]
) -> BinaryIO:
"""将完整曲目导出为对应格式的字节流"""
pass
@abstractmethod
def dump(
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
):
"""将完整曲目导出为对应格式的文件"""
pass
class TrackOutputPlugin(TopInOutBasePlugin, ABC):
"""导出用插件的抽象基类-单个音轨"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.FUNCTION_EXPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def dumpbytes(
self, data: "SingleTrack", config: Optional[PluginConfig]
) -> BinaryIO:
"""将单个音轨导出为对应格式的字节流"""
pass
@abstractmethod
def dump(
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
):
"""将单个音轨导出为对应格式的文件"""
pass
class ServicePlugin(TopBasePlugin, ABC):
"""服务插件抽象基类"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.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], *args) -> None:
"""服务插件的运行逻辑"""
pass
class LibraryPlugin(TopBasePlugin, ABC):
"""插件依赖库的抽象基类"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginType.LIBRARY:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
# 怎么?
# 插件的彼此依赖就不需要什么调用了吧
class PluginRegistry:
"""插件注册管理器"""
def __init__(self):
self._music_input_plugins: List[MusicInputPlugin] = []
self._track_input_plugins: List[TrackInputPlugin] = []
self._music_operate_plugins: List[MusicOperatePlugin] = []
self._track_operate_plugins: List[TrackOperatePlugin] = []
self._music_output_plugins: List[MusicOutputPlugin] = []
self._track_output_plugins: List[TrackOutputPlugin] = []
self._service_plugins: List[ServicePlugin] = []
self._library_plugins: List[LibraryPlugin] = []
def register_music_input_plugin(self, plugin_class: type) -> None:
"""注册输入插件-整首曲目"""
plugin_instance = plugin_class()
self._music_input_plugins.append(plugin_instance)
def register_track_input_plugin(self, plugin_class: type) -> None:
"""注册输入插件-单个音轨"""
plugin_instance = plugin_class()
self._track_input_plugins.append(plugin_instance)
def register_music_operate_plugin(self, plugin_class: type) -> None:
"""注册曲目处理插件"""
plugin_instance = plugin_class()
self._music_operate_plugins.append(plugin_instance)
def register_track_operate_plugin(self, plugin_class: type) -> None:
"""注册音轨处理插件"""
plugin_instance = plugin_class()
self._track_operate_plugins.append(plugin_instance)
def register_music_output_plugin(self, plugin_class: type) -> None:
"""注册输出插件-整首曲目"""
plugin_instance = plugin_class()
self._music_output_plugins.append(plugin_instance)
def register_track_output_plugin(self, plugin_class: type) -> None:
"""注册输出插件-单个音轨"""
plugin_instance = plugin_class()
self._track_output_plugins.append(plugin_instance)
def register_service_plugin(self, plugin_class: type) -> None:
"""注册服务插件"""
plugin_instance = plugin_class()
self._service_plugins.append(plugin_instance)
def register_library_plugin(self, plugin_class: type) -> None:
"""注册支持库插件"""
plugin_instance = plugin_class()
self._library_plugins.append(plugin_instance)
def get_music_input_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[MusicInputPlugin, None, None]:
"""通过指定输入的文件或格式,以获取对应的全曲导入用插件"""
if isinstance(filepath_or_format, str):
for plugin in self._music_input_plugins:
if plugin.can_handle_format(filepath_or_format):
yield plugin
elif isinstance(filepath_or_format, Path):
for plugin in self._music_input_plugins:
if plugin.can_handle_file(filepath_or_format):
yield plugin
else:
raise ParameterTypeError(
"用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
type(filepath_or_format), filepath_or_format
)
)
def get_track_input_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[TrackInputPlugin, None, None]:
"""通过指定输入的文件或格式,以获取对应的单音轨导入用插件"""
if isinstance(filepath_or_format, str):
for plugin in self._track_input_plugins:
if plugin.can_handle_format(filepath_or_format):
yield plugin
elif isinstance(filepath_or_format, Path):
for plugin in self._track_input_plugins:
if plugin.can_handle_file(filepath_or_format):
yield plugin
else:
raise ParameterTypeError(
"用于指定“导入单个音轨的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
type(filepath_or_format), filepath_or_format
)
)
def get_music_output_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[MusicOutputPlugin, None, None]:
"""通过指定输出的文件或格式,以获取对应的导出全曲用插件"""
if isinstance(filepath_or_format, str):
for plugin in self._music_output_plugins:
if plugin.can_handle_format(filepath_or_format):
yield plugin
elif isinstance(filepath_or_format, Path):
for plugin in self._music_output_plugins:
if plugin.can_handle_file(filepath_or_format):
yield plugin
else:
raise ParameterTypeError(
"用于指定“全曲数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
type(filepath_or_format), filepath_or_format
)
)
def get_track_output_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[TrackOutputPlugin, None, None]:
"""通过指定输出的文件或格式,以获取对应的导出单个音轨用插件"""
if isinstance(filepath_or_format, str):
for plugin in self._track_output_plugins:
if plugin.can_handle_format(filepath_or_format):
yield plugin
elif isinstance(filepath_or_format, Path):
for plugin in self._track_output_plugins:
if plugin.can_handle_file(filepath_or_format):
yield plugin
else:
raise ParameterTypeError(
"用于指定“单音轨数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
type(filepath_or_format), filepath_or_format
)
)
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPlugin:
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
try:
return max(
filter(
lambda plugin: plugin.metainfo.name == plugin_name,
self._music_input_plugins,
),
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到“用于导入曲目、名为`{}`”的插件".format(plugin_name)
)
def get_track_input_plugin(self, plugin_name: str) -> TrackInputPlugin:
"""获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的"""
try:
return max(
filter(
lambda plugin: plugin.metainfo.name == plugin_name,
self._track_input_plugins,
),
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到“用于导入单个音轨、名为`{}`”的插件".format(plugin_name)
)
def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePlugin:
"""获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的"""
try:
return max(
filter(
lambda plugin: plugin.metainfo.name == plugin_name,
self._music_operate_plugins,
),
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到“用于处理整个曲目、名为`{}`”的插件".format(plugin_name)
)
def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePlugin:
"""获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的"""
try:
return max(
filter(
lambda plugin: plugin.metainfo.name == plugin_name,
self._track_operate_plugins,
),
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到“用于处理单个音轨、名为`{}`”的插件".format(plugin_name)
)
def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPlugin:
"""获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的"""
try:
return max(
filter(
lambda plugin: plugin.metainfo.name == plugin_name,
self._music_output_plugins,
),
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginMetainfoNotFoundError(
"未找到“用于导出完整曲目、名为`{}`”的插件".format(plugin_name)
)
def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPlugin:
"""获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的"""
try:
return max(
filter(
lambda plugin: plugin.metainfo.name == plugin_name,
self._track_output_plugins,
),
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginMetainfoNotFoundError(
"未找到“用于导出单个音轨、名为`{}`”的插件".format(plugin_name)
)
def get_service_plugin(self, plugin_name: str) -> ServicePlugin:
"""获取服务用插件,当名称重叠时,取版本号最大的"""
try:
return max(
filter(
lambda plugin: plugin.metainfo.name == plugin_name,
self._service_plugins,
),
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到名为`{}`的服务用插件".format(plugin_name)
)
def get_library_plugin(self, plugin_name: str) -> LibraryPlugin:
"""获取依赖库类插件,当名称重叠时,取版本号最高的"""
try:
return max(
filter(
lambda plugin: plugin.metainfo.name == plugin_name,
self._library_plugins,
),
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到名为`{}`的依赖库插件".format(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, self._track_input_plugins
)
)
)
def supported_output_formats(self) -> Set[str]:
"""所有支持的导出格式"""
return set(
chain.from_iterable(
plugin.supported_formats
for plugin in chain(
self._music_output_plugins, self._track_output_plugins
)
)
)
plugin_registry = PluginRegistry()
"""全局插件注册表实例"""
def music_input_plugin(metainfo: PluginMetaInformation):
"""全曲输入用插件装饰器"""
def decorator(cls):
global plugin_registry
cls.metainfo = metainfo
plugin_registry.register_music_input_plugin(cls)
return cls
return decorator
def track_input_plugin(metainfo: PluginMetaInformation):
"""单轨输入用插件装饰器"""
def decorator(cls):
global plugin_registry
cls.metainfo = metainfo
plugin_registry.register_track_input_plugin(cls)
return cls
return decorator
def music_operate_plugin(metainfo: PluginMetaInformation):
"""全曲处理用插件装饰器"""
def decorator(cls):
global plugin_registry
cls.metainfo = metainfo
plugin_registry.register_music_operate_plugin(cls)
return cls
return decorator
def track_operate_plugin(metainfo: PluginMetaInformation):
"""音轨处理插件装饰器"""
def decorator(cls):
global plugin_registry
cls.metainfo = metainfo
plugin_registry.register_track_operate_plugin(cls)
return cls
return decorator
def music_output_plugin(metainfo: PluginMetaInformation):
"""乐曲输出用插件装饰器"""
def decorator(cls):
global plugin_registry
cls.metainfo = metainfo
plugin_registry.register_music_output_plugin(cls)
return cls
return decorator
def track_output_plugin(metainfo: PluginMetaInformation):
"""音轨输出用插件装饰器"""
def decorator(cls):
global plugin_registry
cls.metainfo = metainfo
plugin_registry.register_track_output_plugin(cls)
return cls
return decorator
def service_plugin(metainfo: PluginMetaInformation):
"""服务插件装饰器"""
def decorator(cls):
global plugin_registry
cls.metainfo = metainfo
plugin_registry.register_service_plugin(cls)
return cls
return decorator
def library_plugin(metainfo: PluginMetaInformation):
"""支持库插件装饰器"""
def decorator(cls):
global plugin_registry
cls.metainfo = metainfo
plugin_registry.register_library_plugin(cls)
return cls
return decorator

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
存储 音·创 v3 的插件管理和上层设计内容
存储 音·创 v3 的插件接口与管理相关内容
"""
"""
@@ -16,7 +16,471 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import List, Optional, Dict, Generator, Any
from pathlib import Path
from .plugin import MusicInputPlugin, MusicOperatePlugin, MusicOutputPlugin, TrackInputPlugin, TrackOperatePlugin, TrackOutputPlugin, ServicePlugin, LibraryPlugin
import importlib
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple, Union, Generator, Set
from itertools import chain
from ._plugin_abc import (
# 枚举类
PluginType,
# 抽象基类/数据类(插件参数定义)
PluginConfig,
PluginMetaInformation,
# 抽象基类(插件定义)
MusicInputPlugin,
TrackInputPlugin,
MusicOperatePlugin,
TrackOperatePlugin,
MusicOutputPlugin,
TrackOutputPlugin,
ServicePlugin,
LibraryPlugin,
)
from .exceptions import (
PluginMetainfoNotFoundError,
ParameterTypeError,
PluginInstanceNotFoundError,
)
__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",
]
def load_plugin_module(package: Union[Path, str]):
"""自动发现并加载插件包中的插件
参数:
=====
package: Path | str, 可选
插件包路径或名称,当为 Path 类时为路径,为 str 时为包名,切勿混淆。
"""
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)
class PluginRegistry:
"""插件注册管理器(注册表)"""
def __init__(self):
# 实际上在纵容那些有着同样名称的插件……
# (不用 Dict[str`plugin name`, PluginClass`] 的形式)
# 啊,我真的很高尚
# 你真的不会把插件注册两遍吧……对吧?
self._music_input_plugins: Set[MusicInputPlugin] = set()
self._track_input_plugins: Set[TrackInputPlugin] = set()
self._music_operate_plugins: Set[MusicOperatePlugin] = set()
self._track_operate_plugins: Set[TrackOperatePlugin] = set()
self._music_output_plugins: Set[MusicOutputPlugin] = set()
self._track_output_plugins: Set[TrackOutputPlugin] = set()
self._service_plugins: Set[ServicePlugin] = set()
self._library_plugins: Set[LibraryPlugin] = set()
def register_music_input_plugin(self, plugin_class: type) -> None:
"""注册输入插件-整首曲目"""
self._music_input_plugins.add(plugin_class())
def register_track_input_plugin(self, plugin_class: type) -> None:
"""注册输入插件-单个音轨"""
self._track_input_plugins.add(plugin_class())
def register_music_operate_plugin(self, plugin_class: type) -> None:
"""注册曲目处理插件"""
self._music_operate_plugins.add(plugin_class())
def register_track_operate_plugin(self, plugin_class: type) -> None:
"""注册音轨处理插件"""
self._track_operate_plugins.add(plugin_class())
def register_music_output_plugin(self, plugin_class: type) -> None:
"""注册输出插件-整首曲目"""
self._music_output_plugins.add(plugin_class())
def register_track_output_plugin(self, plugin_class: type) -> None:
"""注册输出插件-单个音轨"""
self._track_output_plugins.add(plugin_class())
def register_service_plugin(self, plugin_class: type) -> None:
"""注册服务插件"""
self._service_plugins.add(plugin_class())
def register_library_plugin(self, plugin_class: type) -> None:
"""注册支持库插件"""
self._library_plugins.add(plugin_class())
def get_music_input_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[MusicInputPlugin, None, None]:
"""通过指定输入的文件或格式,以获取对应的全曲导入用插件"""
if isinstance(filepath_or_format, str):
return (
plugin
for plugin in self._music_input_plugins
if plugin.can_handle_format(filepath_or_format)
)
elif isinstance(filepath_or_format, Path):
return (
plugin
for plugin in self._music_input_plugins
if plugin.can_handle_file(filepath_or_format)
)
else:
raise ParameterTypeError(
"用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
type(filepath_or_format), filepath_or_format
)
)
def get_track_input_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[TrackInputPlugin, None, None]:
"""通过指定输入的文件或格式,以获取对应的单音轨导入用插件"""
if isinstance(filepath_or_format, str):
return (
plugin
for plugin in self._track_input_plugins
if plugin.can_handle_format(filepath_or_format)
)
elif isinstance(filepath_or_format, Path):
return (
plugin
for plugin in self._track_input_plugins
if plugin.can_handle_file(filepath_or_format)
)
else:
raise ParameterTypeError(
"用于指定“导入单个音轨的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
type(filepath_or_format), filepath_or_format
)
)
def get_music_output_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[MusicOutputPlugin, None, None]:
"""通过指定输出的文件或格式,以获取对应的导出全曲用插件"""
if isinstance(filepath_or_format, str):
return (
plugin
for plugin in self._music_output_plugins
if plugin.can_handle_format(filepath_or_format)
)
elif isinstance(filepath_or_format, Path):
return (
plugin
for plugin in self._music_output_plugins
if plugin.can_handle_file(filepath_or_format)
)
else:
raise ParameterTypeError(
"用于指定“全曲数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
type(filepath_or_format), filepath_or_format
)
)
def get_track_output_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[TrackOutputPlugin, None, None]:
"""通过指定输出的文件或格式,以获取对应的导出单个音轨用插件"""
if isinstance(filepath_or_format, str):
return (
plugin
for plugin in self._track_output_plugins
if plugin.can_handle_format(filepath_or_format)
)
elif isinstance(filepath_or_format, Path):
return (
plugin
for plugin in self._track_output_plugins
if plugin.can_handle_file(filepath_or_format)
)
else:
raise ParameterTypeError(
"用于指定“单音轨数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
type(filepath_or_format), filepath_or_format
)
)
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPlugin:
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
try:
return max(
[
plugin
for plugin in self._music_input_plugins
if plugin.metainfo.name == plugin_name
],
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到“用于导入曲目、名为`{}`”的插件".format(plugin_name)
)
def get_track_input_plugin(self, plugin_name: str) -> TrackInputPlugin:
"""获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的"""
try:
return max(
[
plugin
for plugin in self._track_input_plugins
if plugin.metainfo.name == plugin_name
],
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到“用于导入单个音轨、名为`{}`”的插件".format(plugin_name)
)
def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePlugin:
"""获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的"""
try:
return max(
[
plugin
for plugin in self._music_operate_plugins
if plugin.metainfo.name == plugin_name
],
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到“用于处理整个曲目、名为`{}`”的插件".format(plugin_name)
)
def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePlugin:
"""获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的"""
try:
return max(
[
plugin
for plugin in self._track_operate_plugins
if plugin.metainfo.name == plugin_name
],
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到“用于处理单个音轨、名为`{}`”的插件".format(plugin_name)
)
def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPlugin:
"""获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的"""
try:
return max(
[
plugin
for plugin in self._music_output_plugins
if plugin.metainfo.name == plugin_name
],
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginMetainfoNotFoundError(
"未找到“用于导出完整曲目、名为`{}`”的插件".format(plugin_name)
)
def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPlugin:
"""获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的"""
try:
return max(
[
plugin
for plugin in self._track_output_plugins
if plugin.metainfo.name == plugin_name
],
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginMetainfoNotFoundError(
"未找到“用于导出单个音轨、名为`{}`”的插件".format(plugin_name)
)
def get_service_plugin(self, plugin_name: str) -> ServicePlugin:
"""获取服务用插件,当名称重叠时,取版本号最大的"""
try:
return max(
[
plugin
for plugin in self._service_plugins
if plugin.metainfo.name == plugin_name
],
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到名为`{}`的服务用插件".format(plugin_name)
)
def get_library_plugin(self, plugin_name: str) -> LibraryPlugin:
"""获取依赖库类插件,当名称重叠时,取版本号最高的"""
try:
return max(
[
plugin
for plugin in self._library_plugins
if plugin.metainfo.name == plugin_name
],
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到名为`{}`的依赖库插件".format(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, self._track_input_plugins
)
)
)
def supported_output_formats(self) -> Set[str]:
"""所有支持的导出格式"""
return set(
chain.from_iterable(
plugin.supported_formats
for plugin in chain(
self._music_output_plugins, self._track_output_plugins
)
)
)
__global_plugin_registry = PluginRegistry()
"""全局插件注册表实例"""
def music_input_plugin(metainfo: PluginMetaInformation):
"""全曲输入用插件装饰器"""
def decorator(cls):
global __global_plugin_registry
cls.metainfo = metainfo
__global_plugin_registry.register_music_input_plugin(cls)
return cls
return decorator
def track_input_plugin(metainfo: PluginMetaInformation):
"""单轨输入用插件装饰器"""
def decorator(cls):
global __global_plugin_registry
cls.metainfo = metainfo
__global_plugin_registry.register_track_input_plugin(cls)
return cls
return decorator
def music_operate_plugin(metainfo: PluginMetaInformation):
"""全曲处理用插件装饰器"""
def decorator(cls):
global __global_plugin_registry
cls.metainfo = metainfo
__global_plugin_registry.register_music_operate_plugin(cls)
return cls
return decorator
def track_operate_plugin(metainfo: PluginMetaInformation):
"""音轨处理插件装饰器"""
def decorator(cls):
global __global_plugin_registry
cls.metainfo = metainfo
__global_plugin_registry.register_track_operate_plugin(cls)
return cls
return decorator
def music_output_plugin(metainfo: PluginMetaInformation):
"""乐曲输出用插件装饰器"""
def decorator(cls):
global __global_plugin_registry
cls.metainfo = metainfo
__global_plugin_registry.register_music_output_plugin(cls)
return cls
return decorator
def track_output_plugin(metainfo: PluginMetaInformation):
"""音轨输出用插件装饰器"""
def decorator(cls):
global __global_plugin_registry
cls.metainfo = metainfo
__global_plugin_registry.register_track_output_plugin(cls)
return cls
return decorator
def service_plugin(metainfo: PluginMetaInformation):
"""服务插件装饰器"""
def decorator(cls):
global __global_plugin_registry
cls.metainfo = metainfo
__global_plugin_registry.register_service_plugin(cls)
return cls
return decorator
def library_plugin(metainfo: PluginMetaInformation):
"""支持库插件装饰器"""
def decorator(cls):
global __global_plugin_registry
cls.metainfo = metainfo
__global_plugin_registry.register_library_plugin(cls)
return cls
return decorator

View File

@@ -5,8 +5,8 @@
"""
"""
版权所有 © 2025 金羿 & 玉衡
Copyright © 2025 Eilles & Alioth
版权所有 © 2025 金羿 & 玉衡Alioth
Copyright © 2025 Eilles & YuhengAlioth
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory

View File

@@ -1,4 +1,4 @@
import Musicreater
import Musicreater.old_init as old_init
import Musicreater.experiment
import Musicreater.old_plugin
@@ -16,12 +16,12 @@ from Musicreater.old_plugin.mcstructfile import (
)
MSCT_MAIN = (
Musicreater,
Musicreater.experiment,
old_init,
old_init.experiment,
# Musicreater.previous,
)
MSCT_PLUGIN = (Musicreater.old_plugin,)
MSCT_PLUGIN = (old_init.old_plugin,)
MSCT_PLUGIN_FUNCTION = (
to_addon_pack_in_delay,

View File

@@ -1,5 +1,5 @@
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
[Bilibili: 诸葛亮与八卦阵]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
[Bilibili: 玉衡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
[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
@@ -23,7 +23,7 @@
<p>
[![][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)
[![][python]](https://www.python.org/)
[![][license]](LICENSE)
@@ -83,7 +83,7 @@
**金羿 Eilles**我的世界基岩版指令作者个人开发者B 站不知名 UP
**诸葛亮与八卦阵 bgArray**我的世界基岩版玩家喜欢编程和音乐深圳学生
**玉衡Alioth Alioth**我的世界基岩版玩家喜欢编程和音乐学生
**偷吃不是Touch Touch**我的世界基岩版指令制作者提供测试支持
@@ -97,7 +97,7 @@
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法我们将其改编并应用同时感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力让我们在原本一骑绝尘的摸鱼道路上转向开发
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题辅助我们找到了视窗操作系统下的兼容性问题感谢其反馈延迟播放器出现的重大问题让我们得以修改全部延迟播放错误尤其感谢他对于我们的软件的大力宣传
- 感谢 **Ammelia 艾米利亚**\<QQ2838334637\> 敦促我们进行新的功能开发并为新功能提出了非常优秀的大量建议以及提供的 BDX 导入测试支持为我们的新结构生成算法提供了大量的实际理论支持
- 感谢 **[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)** 对我们项目的支持与宣传非常感谢他为我们提供的服务器
- 感谢 **[神羽 SnowyKami](https://www.sfkm.me/)** 对我们项目的支持与宣传非常感谢他为我们提供的服务器
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误并提醒我们修复一个一直存在的大 bug
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误并提醒修复 bug
- 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题

View File

@@ -1,5 +1,5 @@
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-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: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFEilles-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
[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
@@ -14,7 +14,7 @@
</img>
</p>
<h3 align="center">A free open-source library of <i>Minecraft</i> digital music.</h3>
<h3 align="center">A free and open-source library for handling with <i>Minecraft</i> digital music.</h3>
<p align="center">
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
@@ -22,7 +22,7 @@
<p>
[![][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)
[![][python]](https://www.python.org/)
[![][license]](LICENSE)
@@ -39,7 +39,7 @@
## 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)
@@ -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_
**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

View File

@@ -18,7 +18,7 @@ Terms & Conditions: ./License.md
import os
import Musicreater
import Musicreater.old_init as old_init
from Musicreater.old_plugin.addonpack import (
to_addon_pack_in_delay,
to_addon_pack_in_repeater,
@@ -156,7 +156,7 @@ else:
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]
)
@@ -187,7 +187,7 @@ print(
cvt_method(
cvt_mid,
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:],
)
)
@@ -199,14 +199,14 @@ print(
to_BDX_file_in_score(
cvt_mid,
out_path,
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
*prompts[3:],
)
if playerFormat == 1
else to_BDX_file_in_delay(
cvt_mid,
out_path,
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
*prompts[3:],
)
)

View File

@@ -1,10 +1,10 @@
import Musicreater
import Musicreater.old_init as old_init
import Musicreater.old_plugin
import Musicreater.old_plugin.mcstructfile
print(
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
Musicreater.MidiConvert.from_midi_file(
old_init.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
old_init.MidiConvert.from_midi_file(
input("midi路径:"),
old_exe_format=False,
# note_table_replacement={"note.harp": "note.flute"},

View File

@@ -1,4 +1,4 @@
import Musicreater
import Musicreater.old_init as old_init
import Musicreater.old_plugin
import Musicreater.old_plugin.websocket
@@ -7,9 +7,9 @@ import os
dire = input("midi目录")
print(
Musicreater.old_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
)
for names in os.listdir(
@@ -19,6 +19,6 @@ print(
],
input("服务器地址:"),
int(input("服务器端口:")),
Musicreater.DEFAULT_PROGRESSBAR_STYLE,
old_init.DEFAULT_PROGRESSBAR_STYLE,
)
)

View File

@@ -123,7 +123,7 @@ msc_cvt = Musicreater.experiment.FutureMidiConvertJavaE.from_midi_file(
input("midi路径"),
play_speed=float(input("播放速度:")),
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,
)

View File

@@ -1,13 +1,13 @@
from rich.pretty import pprint
import Musicreater
import Musicreater.old_init as old_init
from Musicreater.utils import (
load_decode_fsq_flush_release,
load_decode_musicsequence_metainfo,
)
msc_seq = Musicreater.MusicSequence.from_mido(
Musicreater.mido.MidiFile(
msc_seq = old_init.MusicSequence.from_mido(
old_init.mido.MidiFile(
"./resources/测试片段.mid",
),
"TEST-测试片段",
@@ -20,7 +20,7 @@ with open("test.fsq", "wb") as f:
f.write(fsq_bytes := msc_seq.encode_dump(flowing_codec_support=True))
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(msc_seq_r)

View File

@@ -1,13 +1,13 @@
from rich.pretty import pprint
import Musicreater
import Musicreater.old_init as old_init
from Musicreater.utils import (
load_decode_msq_flush_release,
load_decode_musicsequence_metainfo,
)
msc_seq = Musicreater.MusicSequence.from_mido(
Musicreater.mido.MidiFile(
msc_seq = old_init.MusicSequence.from_mido(
old_init.mido.MidiFile(
"./resources/测试片段.mid",
),
"TEST-测试片段",
@@ -20,7 +20,7 @@ with open("test.msq", "wb") as f:
f.write(msq_bytes := msc_seq.encode_dump())
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(msc_seq_r)

View 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