mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-27 11:45:39 +00:00
Compare commits
8 Commits
v2
...
v3.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4901cf3dc | ||
|
|
13512df9ce | ||
|
|
1d9931f79d | ||
|
|
841f6e53c6 | ||
|
|
0de959c396 | ||
|
|
734ee2dd66 | ||
|
|
32b7930b26 | ||
|
|
583ca04ac9 |
@@ -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,45 @@ 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 .main import MusicSequence, MidiConvert
|
||||
from .paramcurve import ParamCurve, InterpolationMethod, BoundaryBehaviour
|
||||
|
||||
from .subclass import (
|
||||
from .data import (
|
||||
SingleMusic,
|
||||
SingleTrack,
|
||||
SingleNote,
|
||||
SoundAtmos,
|
||||
MineNote,
|
||||
MineCommand,
|
||||
SingleNoteBox,
|
||||
ProgressBarStyle,
|
||||
mctick2timestr,
|
||||
DEFAULT_PROGRESSBAR_STYLE,
|
||||
CurvableParam,
|
||||
)
|
||||
|
||||
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 .plugins import load_plugin_module
|
||||
|
||||
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,
|
||||
)
|
||||
from .main import MusiCreater
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"__author__",
|
||||
# 参数曲线相关
|
||||
"ParamCurve",
|
||||
"InterpolationMethod",
|
||||
"BoundaryBehaviour",
|
||||
# 音乐数据结构
|
||||
"SingleMusic",
|
||||
"SingleTrack",
|
||||
"SingleNote",
|
||||
"SoundAtmos",
|
||||
"MineNote",
|
||||
"CurvableParam",
|
||||
# 工程项目相关
|
||||
"load_plugin_module",
|
||||
"MusiCreater",
|
||||
]
|
||||
|
||||
632
Musicreater/_plugin_abc.py
Normal file
632
Musicreater/_plugin_abc.py
Normal file
@@ -0,0 +1,632 @@
|
||||
# -*- 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,
|
||||
)
|
||||
|
||||
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: Dict[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(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(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 dumpbytes(
|
||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将完整曲目导出为对应格式的字节流
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleMusic
|
||||
待导出的完整曲目
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
BinaryIO
|
||||
导出后的二进制字节流
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将完整曲目导出为对应格式的文件
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleMusic
|
||||
待导出的完整曲目
|
||||
file_path: Path
|
||||
输出文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
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 dumpbytes(
|
||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将单个音轨导出为对应格式的字节流
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleTrack
|
||||
待导出的单个音轨
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
BinaryIO
|
||||
导出后的二进制字节流
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将单个音轨导出为对应格式的文件
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleTrack
|
||||
待导出的单个音轨
|
||||
file_path: Path
|
||||
输出文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
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], *args) -> None:
|
||||
"""服务插件的运行逻辑
|
||||
|
||||
参数
|
||||
====
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
*args: Any
|
||||
其他运行时参数
|
||||
"""
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
# 怎么?
|
||||
# 插件的彼此依赖就不需要什么调用了吧
|
||||
59
Musicreater/builtin_plugins/midi_read.py
Normal file
59
Musicreater/builtin_plugins/midi_read.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
音·创 v3 内置的 Midi 读取插件
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2026 金羿、玉衡Alioth
|
||||
Copyright © 2026 Eilles, YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
import mido
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from typing import BinaryIO, Optional
|
||||
|
||||
from Musicreater import SingleMusic
|
||||
from Musicreater.plugins import (
|
||||
music_input_plugin,
|
||||
PluginConfig,
|
||||
PluginMetaInformation,
|
||||
PluginTypes,
|
||||
MusicInputPluginBase,
|
||||
)
|
||||
|
||||
|
||||
@music_input_plugin("midi_2_music_plugin")
|
||||
class MidiImport2MusicPlugin(MusicInputPluginBase):
|
||||
"""Midi 音乐数据导入插件"""
|
||||
|
||||
metainfo = PluginMetaInformation(
|
||||
name="Midi 导入插件",
|
||||
author="金羿、玉衡Alioth",
|
||||
description="从 Midi 文件导入音乐数据",
|
||||
version=(0, 0, 1),
|
||||
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
||||
license="Same as Musicreater",
|
||||
)
|
||||
|
||||
supported_formats = ("MID", "MIDI")
|
||||
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: PluginConfig | None
|
||||
) -> SingleMusic:
|
||||
midi_file = mido.MidiFile(file=bytes_buffer_in)
|
||||
return SingleMusic() # =========================== TODO: 等待制作
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
|
||||
"""从 Midi 文件导入音乐数据"""
|
||||
midi_file = mido.MidiFile(filename=file_path)
|
||||
return SingleMusic() # =========================== TODO: 等待制作
|
||||
@@ -5,8 +5,8 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
版权所有 © 2026 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2026 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储音·创新数据存储类
|
||||
存储 音·创 v3 的内部数据类
|
||||
"""
|
||||
|
||||
# WARNING 本文件中使用之功能尚未启用
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿
|
||||
Copyright © 2025 Eilles
|
||||
版权所有 © 2026 金羿
|
||||
Copyright © 2026 Eilles
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -18,54 +16,68 @@ Terms & Conditions: License.md in the root directory
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 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 typing import Optional, Any, List, Tuple, Union, Dict, Sequence, Callable
|
||||
import bisect
|
||||
from typing import (
|
||||
Optional,
|
||||
Any,
|
||||
List,
|
||||
Tuple,
|
||||
Union,
|
||||
Dict,
|
||||
Set,
|
||||
Sequence,
|
||||
Callable,
|
||||
Generator,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Literal,
|
||||
Hashable,
|
||||
TypeVar,
|
||||
)
|
||||
from enum import Enum
|
||||
|
||||
from .types import FittingFunctionType
|
||||
from .constants import MC_PITCHED_INSTRUMENT_LIST
|
||||
|
||||
|
||||
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
|
||||
from .exceptions import SingleNoteDecodeError, ParameterTypeError, ParameterValueError
|
||||
from .paramcurve import ParamCurve
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class SoundAtmos:
|
||||
"""声源方位类"""
|
||||
|
||||
sound_distance: float
|
||||
"""声源距离 方块"""
|
||||
|
||||
sound_azimuth: Tuple[float, float]
|
||||
"""声源方位 角度"""
|
||||
"""声源方位 角度(rV左右 rH上下)"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
distance: Optional[float] = None,
|
||||
azimuth: Optional[Tuple[float, float]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
定义一个发声方位
|
||||
|
||||
Parameters
|
||||
------------
|
||||
distance: float
|
||||
发声源距离玩家的距离(半径 `r`)
|
||||
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
|
||||
azimuth: tuple[float, float]
|
||||
声源方位
|
||||
注:此参数为tuple,包含两个元素,分别表示:
|
||||
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
||||
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
||||
"""
|
||||
|
||||
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
|
||||
"""声源方位"""
|
||||
@@ -103,7 +115,7 @@ class SoundAtmos:
|
||||
|
||||
@property
|
||||
def position_displacement(self) -> Tuple[float, float, float]:
|
||||
"""声像位移"""
|
||||
"""声像位移,直接可应用于我的世界的相对视角的坐标参考系中(^x ^y ^z)"""
|
||||
dk1 = self.sound_distance * round(cos(radians(self.sound_azimuth[1])), 8)
|
||||
return (
|
||||
-dk1 * round(sin(radians(self.sound_azimuth[0])), 8),
|
||||
@@ -122,13 +134,13 @@ class SingleNote:
|
||||
velocity: int
|
||||
"""力度"""
|
||||
|
||||
start_tick: int
|
||||
start_time: int
|
||||
"""开始之时 命令刻"""
|
||||
|
||||
duration: int
|
||||
"""音符持续时间 命令刻"""
|
||||
|
||||
high_precision_time: int
|
||||
high_precision_start_time: int
|
||||
"""高精度开始时间偏量 1/1250 秒"""
|
||||
|
||||
extra_info: Dict[str, Any]
|
||||
@@ -161,14 +173,6 @@ class SingleNote:
|
||||
高精度的开始时间偏移量(1/1250秒)
|
||||
is_percussion: bool
|
||||
是否作为打击乐器
|
||||
distance: float
|
||||
发声源距离玩家的距离(半径 `r`)
|
||||
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
|
||||
azimuth: tuple[float, float]
|
||||
声源方位
|
||||
注:此参数为tuple,包含两个元素,分别表示:
|
||||
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
||||
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
||||
extra_information: Dict[str, Any]
|
||||
附加信息,尽量存储为字典
|
||||
|
||||
@@ -181,11 +185,11 @@ class SingleNote:
|
||||
"""midi音高"""
|
||||
self.velocity: int = midi_velocity
|
||||
"""响度(力度)"""
|
||||
self.start_tick: int = start_time
|
||||
self.start_time: int = start_time
|
||||
"""开始之时 命令刻"""
|
||||
self.duration: int = last_time
|
||||
"""音符持续时间 命令刻"""
|
||||
self.high_precision_time: int = mass_precision_time
|
||||
self.high_precision_start_time: int = mass_precision_time
|
||||
"""高精度开始时间偏量 0.4 毫秒"""
|
||||
|
||||
self.extra_info = extra_information if extra_information else {}
|
||||
@@ -208,13 +212,21 @@ class SingleNote:
|
||||
last_time=duration_,
|
||||
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
|
||||
)
|
||||
except:
|
||||
except Exception as e:
|
||||
# 我也不知道为什么这里要放一个异常处理
|
||||
# 之前用到过吗?
|
||||
# —— 2026.01.25 金羿
|
||||
print(
|
||||
"[Error] 单音符解析错误,字节码`{}`,{}启用高精度时间偏移\n".format(
|
||||
"[Exception] 单音符解析错误,字节码`{}`,{}启用高精度时间偏移\n".format(
|
||||
code_buffer, "已" if is_high_time_precision else "未"
|
||||
)
|
||||
)
|
||||
raise
|
||||
raise SingleNoteDecodeError(
|
||||
e,
|
||||
"技术信息:\nGROUP1\t`{}`\nCODE_BUFFER\t`{}`".format(
|
||||
group_1, code_buffer
|
||||
),
|
||||
)
|
||||
|
||||
def encode(self, is_high_time_precision: bool = True) -> bytes:
|
||||
"""
|
||||
@@ -244,14 +256,14 @@ class SingleNote:
|
||||
return (
|
||||
(
|
||||
(
|
||||
((((self.note_pitch << 7) + self.velocity) << 17) + self.start_tick)
|
||||
((((self.note_pitch << 7) + self.velocity) << 17) + self.start_time)
|
||||
<< 17
|
||||
)
|
||||
+ self.duration
|
||||
).to_bytes(6, "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
|
||||
else b""
|
||||
)
|
||||
@@ -270,7 +282,9 @@ class SingleNote:
|
||||
self.extra_info[key[i]] = value[i]
|
||||
else:
|
||||
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
||||
raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
|
||||
raise ParameterTypeError(
|
||||
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||
)
|
||||
|
||||
def get_info(self, key: str, default: Any = None) -> Any:
|
||||
"""获取附加信息"""
|
||||
@@ -280,9 +294,9 @@ class SingleNote:
|
||||
return "TrackedNote(Pitch = {}, Velocity = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
|
||||
self.note_pitch,
|
||||
self.velocity,
|
||||
self.start_tick,
|
||||
self.start_time,
|
||||
self.duration,
|
||||
self.high_precision_time,
|
||||
self.high_precision_start_time,
|
||||
) + (
|
||||
", ExtraData = {})".format(self.extra_info) if include_extra_data else ")"
|
||||
)
|
||||
@@ -296,18 +310,18 @@ class SingleNote:
|
||||
return (
|
||||
self.note_pitch,
|
||||
self.velocity,
|
||||
self.start_tick,
|
||||
self.start_time,
|
||||
self.duration,
|
||||
self.high_precision_time,
|
||||
self.high_precision_start_time,
|
||||
)
|
||||
|
||||
def __dict__(self):
|
||||
return {
|
||||
"Pitch": self.note_pitch,
|
||||
"Velocity": self.velocity,
|
||||
"StartTick": self.start_tick,
|
||||
"StartTick": self.start_time,
|
||||
"Duration": self.duration,
|
||||
"TimeOffset": self.high_precision_time,
|
||||
"TimeOffset": self.high_precision_start_time,
|
||||
"ExtraData": self.extra_info,
|
||||
}
|
||||
|
||||
@@ -319,25 +333,97 @@ class SingleNote:
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
"""比较自己是否在开始时间上早于另一个音符"""
|
||||
if self.start_tick == other.start_tick:
|
||||
return self.high_precision_time < other.high_precision_time
|
||||
if self.start_time == other.start_tick:
|
||||
return self.high_precision_start_time < other.high_precision_time
|
||||
else:
|
||||
return self.start_tick < other.start_tick
|
||||
return self.start_time < other.start_tick
|
||||
|
||||
def __gt__(self, other) -> bool:
|
||||
"""比较自己是否在开始时间上晚于另一个音符"""
|
||||
if self.start_tick == other.start_tick:
|
||||
return self.high_precision_time > other.high_precision_time
|
||||
if self.start_time == other.start_tick:
|
||||
return self.high_precision_start_time > other.high_precision_time
|
||||
else:
|
||||
return self.start_tick > other.start_tick
|
||||
return self.start_time > other.start_tick
|
||||
|
||||
|
||||
class SingleTrack(list):
|
||||
class CurvableParam(str, Enum):
|
||||
"""可曲线化的参数枚举类"""
|
||||
|
||||
PITCH = "adjust_note_pitch"
|
||||
VELOCITY = "adjust_note_velocity"
|
||||
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"""
|
||||
velocity: float
|
||||
"""力度"""
|
||||
volume: float
|
||||
"""音量"""
|
||||
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,
|
||||
sound_volume: float,
|
||||
is_persiced_time: bool,
|
||||
is_percussive_note: bool,
|
||||
sound_position: SoundAtmos,
|
||||
adjust_note_pitch: float = 0.0,
|
||||
adjust_note_velocity: 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.note_pitch + adjust_note_pitch,
|
||||
instrument=note_instrument,
|
||||
velocity=note.velocity + adjust_note_velocity,
|
||||
volume=sound_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
|
||||
"""轨道之名称"""
|
||||
|
||||
is_enabled: bool = True
|
||||
"""该音轨是否启用"""
|
||||
|
||||
track_instrument: str
|
||||
"""乐器ID"""
|
||||
|
||||
@@ -353,7 +439,7 @@ class SingleTrack(list):
|
||||
sound_position: SoundAtmos
|
||||
"""声像方位"""
|
||||
|
||||
argument_curves: Dict[str, FittingFunctionType]
|
||||
argument_curves: Dict[CurvableParam, Union[ParamCurve, Literal[None]]]
|
||||
"""参数曲线"""
|
||||
|
||||
extra_info: Dict[str, Any]
|
||||
@@ -390,13 +476,111 @@ class SingleTrack(list):
|
||||
|
||||
self.extra_info = extra_information if extra_information else {}
|
||||
|
||||
self.argument_curves = {item: None for item in CurvableParam}
|
||||
|
||||
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
|
||||
) -> Generator[SingleNote, None, None]:
|
||||
"""通过开始时间和结束时间来获取音符"""
|
||||
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(
|
||||
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.track_instrument,
|
||||
sound_volume=self.track_volume,
|
||||
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
|
||||
def note_amount(self) -> int:
|
||||
"""音符数"""
|
||||
return len(self)
|
||||
|
||||
@property
|
||||
def track_notes(self) -> List[SingleNote]:
|
||||
"""音符列表"""
|
||||
return self
|
||||
|
||||
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||
"""设置附加信息"""
|
||||
if isinstance(key, str):
|
||||
@@ -410,12 +594,193 @@ class SingleTrack(list):
|
||||
self.extra_info[key[i]] = value[i]
|
||||
else:
|
||||
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
||||
raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
|
||||
raise ParameterTypeError(
|
||||
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||
)
|
||||
|
||||
def get_info(self, key: str, default: Any = None) -> Any:
|
||||
"""获取附加信息"""
|
||||
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,
|
||||
name: str = "未命名乐曲",
|
||||
creator: str = "未命名制作者",
|
||||
original_author: str = "未命名原作者",
|
||||
description: str = "未命名简介",
|
||||
credits: str = "未命名版权信息",
|
||||
*args: SingleTrack,
|
||||
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 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
|
||||
) -> 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.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 -*-
|
||||
|
||||
"""
|
||||
存放一些报错类型
|
||||
存储 音·创 v3 用到的一些报错类型
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||
Copyright © 2026 Eilles & YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -16,147 +16,251 @@ 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 MSCTBaseException(Exception):
|
||||
"""音·创 的所有错误均继承于此"""
|
||||
|
||||
class MusicreaterBaseException(Exception):
|
||||
"""音·创 v3 的所有错误均继承于此"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音·创 的所有错误均继承于此"""
|
||||
super().__init__("音·创", *args)
|
||||
super().__init__("[音·创] - ", *args)
|
||||
|
||||
def meow(
|
||||
self,
|
||||
):
|
||||
def meow(self):
|
||||
for i in self.args:
|
||||
print(i + "喵!")
|
||||
print(i + "喵~", end=":")
|
||||
|
||||
def crash_it(self):
|
||||
raise self
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "".join(self.args)
|
||||
|
||||
class MidiFormatException(MSCTBaseException):
|
||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||
|
||||
# =====================================
|
||||
# NOTE
|
||||
# 面对用户时候爆出去的我们认为这就是“外部错误”
|
||||
# 如果是在程序内部数据传输等情况下出现的就是“内部错误”
|
||||
# 例如,无法读取文件,这就是一个外部错误
|
||||
# 某个参数的数据类型错误,这就是内部错误
|
||||
# =====================================
|
||||
|
||||
|
||||
class MusicreaterInnerlyError(MusicreaterBaseException):
|
||||
"""内部错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||
super().__init__("MIDI 格式错误", *args)
|
||||
"""内部错误(面向开发者的报错信息)"""
|
||||
super().__init__("内部错误 - ", *args)
|
||||
|
||||
|
||||
class MidiDestroyedError(MSCTBaseException):
|
||||
"""Midi文件损坏"""
|
||||
class MusicreaterOuterlyError(MusicreaterBaseException):
|
||||
"""外部错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Midi文件损坏"""
|
||||
super().__init__("MIDI文件损坏:无法读取 MIDI 文件", *args)
|
||||
"""外部错误(面向用户的报错信息)"""
|
||||
super().__init__("外部错误 - ", *args)
|
||||
|
||||
|
||||
# class MidiUnboundError(MSCTBaseException):
|
||||
# """未定义Midi对象(无用)"""
|
||||
|
||||
# def __init__(self, *args):
|
||||
# """未绑定Midi对象"""
|
||||
# super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args)
|
||||
# 此错误在本版本内已经不再使用
|
||||
|
||||
|
||||
class CommandFormatError(MSCTBaseException, RuntimeError):
|
||||
"""指令格式与目标格式不匹配而引起的错误"""
|
||||
class InnerlyParameterError(MusicreaterInnerlyError):
|
||||
"""内部传参错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""指令格式与目标格式不匹配而引起的错误"""
|
||||
super().__init__("指令格式不匹配", *args)
|
||||
"""参数错误"""
|
||||
super().__init__("传参错误 - ", *args)
|
||||
|
||||
|
||||
# class CrossNoteError(MidiFormatException):
|
||||
# """同通道下同音符交叉出现所产生的错误"""
|
||||
|
||||
# def __init__(self, *args):
|
||||
# """同通道下同音符交叉出现所产生的错误"""
|
||||
# super().__init__("同通道下同音符交叉", *args)
|
||||
# 这TM是什么错误?
|
||||
# 我什么时候写的这玩意?
|
||||
# 我哪知道这说的是啥?
|
||||
# !!!
|
||||
# 我知道这是什么了 —— 金羿 2025 0401
|
||||
# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息
|
||||
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
||||
|
||||
|
||||
class NotDefineTempoError(MidiFormatException):
|
||||
"""没有Tempo设定导致时间无法计算的错误"""
|
||||
class ParameterTypeError(InnerlyParameterError, TypeError):
|
||||
"""参数类型错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""没有Tempo设定导致时间无法计算的错误"""
|
||||
super().__init__("在曲目开始时没有声明 Tempo(未指定拍长)", *args)
|
||||
"""参数类型错误"""
|
||||
super().__init__("参数类型错误:", *args)
|
||||
|
||||
|
||||
class ChannelOverFlowError(MidiFormatException):
|
||||
"""一个midi中含有过多的通道"""
|
||||
|
||||
def __init__(self, max_channel=16, *args):
|
||||
"""一个midi中含有过多的通道"""
|
||||
super().__init__("含有过多的通道(数量应≤{})".format(max_channel), *args)
|
||||
|
||||
|
||||
class NotDefineProgramError(MidiFormatException):
|
||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||
class ParameterValueError(InnerlyParameterError, ValueError):
|
||||
"""参数值存在错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||
super().__init__("未指定演奏乐器", *args)
|
||||
"""参数其值存在错误"""
|
||||
super().__init__("参数数值错误:", *args)
|
||||
|
||||
|
||||
class NoteOnOffMismatchError(MidiFormatException):
|
||||
"""音符开音和停止不匹配的错误"""
|
||||
class PluginNotSpecifiedError(InnerlyParameterError, LookupError):
|
||||
"""未指定插件"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音符开音和停止不匹配的错误"""
|
||||
super().__init__("音符不匹配", *args)
|
||||
"""未指定插件"""
|
||||
super().__init__("未指定插件:", *args)
|
||||
|
||||
|
||||
class LyricMismatchError(MSCTBaseException):
|
||||
"""歌词匹配解析错误"""
|
||||
class OuterlyParameterError(MusicreaterOuterlyError):
|
||||
"""外部参数错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""有可能产生了错误的歌词解析"""
|
||||
super().__init__("歌词解析错误", *args)
|
||||
"""参数错误"""
|
||||
super().__init__("参数错误 - ", *args)
|
||||
|
||||
|
||||
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
|
||||
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
|
||||
"""以0作为播放速度的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""以0作为播放速度的错误"""
|
||||
super().__init__("播放速度为零", *args)
|
||||
super().__init__("播放速度为零:", *args)
|
||||
|
||||
|
||||
class IllegalMinimumVolumeError(MSCTBaseException, ValueError):
|
||||
class IllegalMinimumVolumeError(OuterlyParameterError, ValueError):
|
||||
"""最小播放音量有误的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""最小播放音量错误"""
|
||||
super().__init__("最小播放音量超出范围", *args)
|
||||
super().__init__("最小播放音量超出范围:", *args)
|
||||
|
||||
|
||||
class MusicSequenceDecodeError(MSCTBaseException):
|
||||
"""音乐序列解码错误"""
|
||||
class FileFormatNotSupportedError(MusicreaterOuterlyError):
|
||||
"""不支持的文件格式"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音乐序列无法正确解码的错误"""
|
||||
super().__init__("解码音符序列文件时出现问题", *args)
|
||||
"""文件格式不受支持"""
|
||||
super().__init__("不支持的文件格式:", *args)
|
||||
|
||||
|
||||
class MusicSequenceTypeError(MSCTBaseException):
|
||||
"""音乐序列类型错误"""
|
||||
class NoteBinaryDecodeError(MusicreaterOuterlyError):
|
||||
"""音乐存储二进制数据解码错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""无法识别音符序列字节码的类型"""
|
||||
super().__init__("错误的音符序列字节类型", *args)
|
||||
"""音乐存储二进制数据无法正确解码"""
|
||||
super().__init__("解码音乐存储二进制数据时出现问题 - ", *args)
|
||||
|
||||
|
||||
class MusicSequenceVerificationFailed(MusicSequenceDecodeError):
|
||||
"""音乐序列校验失败"""
|
||||
class SingleNoteDecodeError(NoteBinaryDecodeError):
|
||||
"""单个音符的二进制数据解码错误"""
|
||||
|
||||
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 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)
|
||||
|
||||
1991
Musicreater/main.py
1991
Musicreater/main.py
File diff suppressed because it is too large
Load Diff
583
Musicreater/paramcurve.py
Normal file
583
Musicreater/paramcurve.py
Normal file
@@ -0,0 +1,583 @@
|
||||
# -*- 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
|
||||
from enum import Enum
|
||||
import bisect
|
||||
|
||||
from .types import FittingFunctionType
|
||||
|
||||
|
||||
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[FittingFunctionType] = 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: FittingFunctionType
|
||||
"""默认(未指定区间时的)关键帧插值模式"""
|
||||
|
||||
boundary_behaviour: BoundaryBehaviour
|
||||
"""边界行为,控制参数曲线在已定义的范围外的返回值"""
|
||||
|
||||
_keys: List[Keyframe]
|
||||
"""关键帧列表"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_value: float = 0.0,
|
||||
default_interpolation_function: FittingFunctionType = 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[FittingFunctionType] = 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[FittingFunctionType] = 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: FittingFunctionType):
|
||||
"""设置默认插值函数。"""
|
||||
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
|
||||
424
Musicreater/plugins.py
Normal file
424
Musicreater/plugins.py
Normal file
@@ -0,0 +1,424 @@
|
||||
# -*- 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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
__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 时为包名,切勿混淆。
|
||||
"""
|
||||
|
||||
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):
|
||||
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] = {}
|
||||
|
||||
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),
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _register_plugin(cls_dict: dict, plg_class: type, 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
|
||||
)
|
||||
)
|
||||
cls_dict[plg_id] = plg_class()
|
||||
|
||||
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: Dict[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):
|
||||
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:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于{}、名为`{}`”的插件".format(plugin_usage, plugin_name)
|
||||
)
|
||||
|
||||
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 -*-
|
||||
|
||||
"""
|
||||
存放数据类型的定义
|
||||
存储 音·创 v3 定义的一些数据类型,可以用于类型检查器
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||
Copyright © 2026 Eilles & YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -16,58 +16,10 @@ Terms & Conditions: License.md in the root directory
|
||||
# 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,],
|
||||
]
|
||||
|
||||
|
||||
|
||||
10
README.md
10
README.md
@@ -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)
|
||||
@@ -63,7 +63,7 @@
|
||||
pip install --upgrade -i https://pypi.python.org/simple Musicreater
|
||||
```
|
||||
|
||||
- 克隆仓库并安装(最新版本但**不推荐**)
|
||||
- 克隆仓库并安装(最新内容但**不推荐**)
|
||||
|
||||
```bash
|
||||
git clone https://gitee.com/TriM-Organization/Musicreater.git
|
||||
@@ -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\> 反馈附加包生成时缺少描述和标题的问题。
|
||||
|
||||
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: 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)
|
||||
@@ -35,11 +35,11 @@
|
||||
|
||||
[简体中文 🇨🇳](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🚀
|
||||
|
||||
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
|
||||
|
||||
|
||||
48
TO-DO.md
Normal file
48
TO-DO.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 任务清单
|
||||
|
||||
## 待办事项
|
||||
- 乐曲文件格式设计
|
||||
目前想到的是:
|
||||
1. 使用 `.MCT` 作为项目文件的后缀,然后考虑一下格式是否和之前的 MusicSequence 兼容,如果兼容的话可以照旧用 `.MSQ`,如不的话,可以试试想一个新的后缀名作为数据文件后缀
|
||||
2. 要求数据文件支持完全流式读入
|
||||
|
||||
- 音轨静音处理
|
||||
当前没有处理
|
||||
|
||||
- 优化音轨的存储方式
|
||||
当前是用列表,且每一次变动元素都要重新排序,这样消耗太大了,需要优化,改用最小堆形式(heapq)
|
||||
|
||||
- 移植 v2 功能到内置插件
|
||||
目前 v2 的功能有很多,都要移植到 v3。
|
||||
1. 导入 Midi 文件到全曲
|
||||
2. 导入 Midi 文件到指定轨道
|
||||
3. 导出到延迟播放器的结构文件(MCSTRUCTURE、BDX)
|
||||
4. 导出到延迟播放器的附加包
|
||||
5. 导出到积分板播放器的以上两种形式
|
||||
6. 导出到中继器播放器的以上两种形式
|
||||
7. 在 WebSocket 播放器中播放
|
||||
8. 导出到支持神羽资源包的以上 7 种形式
|
||||
9. 对于 Midi 歌词的实验性功能
|
||||
10. 对于 Java 版本适配的实验性功能
|
||||
11. 对于听感优化的实验性功能(插值、偏移)
|
||||
|
||||
- 测试参数曲线的功能
|
||||
|
||||
- 支持导出音符盒构成的音乐
|
||||
|
||||
- 支持导出成 schematic 结构
|
||||
|
||||
## 讨论
|
||||
|
||||
1. [x] 是否应该在插件注册表 PluginRegistry 中采用 `Dict[插件名, 插件对象]` 的形式存储插件?
|
||||
当前不采用这种方式是认为可以兼容一些极端场景下用户将一堆同名插件放在一起的情况。但是就算是插件放在一起,我们也可以有选择地读入注册表,比如依照版本号只读取最高版本的插件,并不需要全部存储在插件注册表中。所以其实用字典来存储是有利的?吗?
|
||||
|
||||
**当前已解决**
|
||||
|
||||
引入了插件惟一识别码之后当然是采用 `Dict[插件唯一识别码, 插件对象]` 来存储插件了~之前插件名称的内容是我想得太浅了,我写完所有代码之后才想到插件名称是中文还带空格的任意字符串……
|
||||
|
||||
2. 服务插件到底该怎么写?总不能留着一个 PluginType.SERVICE 的插件一直空在那里吧……
|
||||
|
||||
3. 插件依赖性的优化。目前没有处理各个插件依赖关系的问题,如果插件之间彼此依赖要怎么做?
|
||||
我的想法是,这个依赖的处理由调用端来完成。比如我们的 伶伦工作站 是以 音·创 为核心的一个可视化数字音频工作站。
|
||||
那么应该由伶伦来处理依赖关系并加载之。
|
||||
@@ -18,8 +18,8 @@ Terms & Conditions: License.md in the root directory
|
||||
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from .exceptions import *
|
||||
from .main import (
|
||||
from .old_exceptions import *
|
||||
from .old_main import (
|
||||
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
|
||||
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
|
||||
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||
@@ -30,7 +30,7 @@ from .main import (
|
||||
|
||||
from .constants import MIDI_PAN, MIDI_PROGRAM, MIDI_VOLUME
|
||||
from .subclass import *
|
||||
from .types import ChannelType, FittingFunctionType
|
||||
from .old_types import ChannelType, FittingFunctionType
|
||||
from .utils import *
|
||||
|
||||
|
||||
165
old-things/Musicreater/old_exceptions.py
Normal file
165
old-things/Musicreater/old_exceptions.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# -*- 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 MidiFormatException(MSCTBaseException):
|
||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||
super().__init__("MIDI 格式错误", *args)
|
||||
|
||||
|
||||
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
|
||||
# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息
|
||||
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
||||
|
||||
|
||||
class NotDefineTempoError(MidiFormatException):
|
||||
"""没有Tempo设定导致时间无法计算的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""没有Tempo设定导致时间无法计算的错误"""
|
||||
super().__init__("在曲目开始时没有声明 Tempo(未指定拍长)", *args)
|
||||
|
||||
|
||||
class ChannelOverFlowError(MidiFormatException):
|
||||
"""一个midi中含有过多的通道"""
|
||||
|
||||
def __init__(self, max_channel=16, *args):
|
||||
"""一个midi中含有过多的通道"""
|
||||
super().__init__("含有过多的通道(数量应≤{})".format(max_channel), *args)
|
||||
|
||||
|
||||
class NotDefineProgramError(MidiFormatException):
|
||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||
super().__init__("未指定演奏乐器", *args)
|
||||
|
||||
|
||||
class NoteOnOffMismatchError(MidiFormatException):
|
||||
"""音符开音和停止不匹配的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音符开音和停止不匹配的错误"""
|
||||
super().__init__("音符不匹配", *args)
|
||||
|
||||
|
||||
class LyricMismatchError(MSCTBaseException):
|
||||
"""歌词匹配解析错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""有可能产生了错误的歌词解析"""
|
||||
super().__init__("歌词解析错误", *args)
|
||||
|
||||
# 已重构
|
||||
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
|
||||
"""以0作为播放速度的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""以0作为播放速度的错误"""
|
||||
super().__init__("播放速度为零", *args)
|
||||
|
||||
# 已重构
|
||||
class IllegalMinimumVolumeError(MSCTBaseException, ValueError):
|
||||
"""最小播放音量有误的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""最小播放音量错误"""
|
||||
super().__init__("最小播放音量超出范围", *args)
|
||||
|
||||
|
||||
# 已重构
|
||||
class MusicSequenceDecodeError(MSCTBaseException):
|
||||
"""音乐序列解码错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音乐序列无法正确解码的错误"""
|
||||
super().__init__("解码音符序列文件时出现问题", *args)
|
||||
|
||||
|
||||
# 已重构
|
||||
class MusicSequenceTypeError(MSCTBaseException):
|
||||
"""音乐序列类型错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""无法识别音符序列字节码的类型"""
|
||||
super().__init__("错误的音符序列字节类型", *args)
|
||||
|
||||
|
||||
# 已重构
|
||||
class MusicSequenceVerificationFailed(MusicSequenceDecodeError):
|
||||
"""音乐序列校验失败"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音符序列文件与其校验值不一致"""
|
||||
super().__init__("音符序列文件校验失败", *args)
|
||||
144
old-things/Musicreater/old_init.py
Normal file
144
old-things/Musicreater/old_init.py
Normal 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,
|
||||
)
|
||||
1816
old-things/Musicreater/old_main.py
Normal file
1816
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
|
||||
from typing import Literal, Optional, Tuple
|
||||
|
||||
from ...main import MidiConvert
|
||||
from ...old_main import MidiConvert
|
||||
from ...subclass import ProgressBarStyle
|
||||
from ..archive import behavior_mcpack_manifest, compress_zipfile
|
||||
from ..mcstructure import (
|
||||
@@ -17,7 +17,7 @@ from typing import Optional
|
||||
|
||||
import brotli
|
||||
|
||||
from ...main import MidiConvert
|
||||
from ...old_main import MidiConvert
|
||||
from ...subclass import MineCommand, ProgressBarStyle
|
||||
from ..bdx import (
|
||||
bdx_move,
|
||||
@@ -14,7 +14,7 @@ Terms & Conditions: License.md in the root directory
|
||||
import os
|
||||
from typing import Literal
|
||||
|
||||
from ...main import MidiConvert
|
||||
from ...old_main import MidiConvert
|
||||
from ...subclass import MineCommand
|
||||
from ..mcstructure import (
|
||||
COMPABILITY_VERSION_117,
|
||||
@@ -16,8 +16,8 @@ Terms & Conditions: License.md in the root directory
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
from ..exceptions import NotDefineProgramError, ZeroSpeedError
|
||||
from ..main import MidiConvert
|
||||
from ..old_exceptions import NotDefineProgramError, ZeroSpeedError
|
||||
from ..old_main import MidiConvert
|
||||
from ..subclass import MineCommand
|
||||
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
|
||||
|
||||
from ...main import MidiConvert
|
||||
from ...old_main import MidiConvert
|
||||
from ...subclass import MineCommand, ProgressBarStyle
|
||||
|
||||
|
||||
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,],
|
||||
]
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ from .constants import (
|
||||
MM_INSTRUMENT_DEVIATION_TABLE,
|
||||
MM_INSTRUMENT_RANGE_TABLE,
|
||||
)
|
||||
from .exceptions import MusicSequenceDecodeError
|
||||
from .old_exceptions import MusicSequenceDecodeError
|
||||
from .subclass import MineNote, mctick2timestr, SingleNoteBox
|
||||
from .types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
||||
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
||||
|
||||
|
||||
def empty_midi_channels(
|
||||
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.plugin
|
||||
import Musicreater.old_plugin
|
||||
|
||||
# import Musicreater.previous
|
||||
from Musicreater.plugin.addonpack import (
|
||||
from Musicreater.old_plugin.addonpack import (
|
||||
to_addon_pack_in_delay,
|
||||
to_addon_pack_in_repeater,
|
||||
to_addon_pack_in_score,
|
||||
)
|
||||
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||
from Musicreater.plugin.mcstructfile import (
|
||||
from Musicreater.old_plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||
from Musicreater.old_plugin.mcstructfile import (
|
||||
to_mcstructure_file_in_delay,
|
||||
to_mcstructure_file_in_repeater,
|
||||
to_mcstructure_file_in_score,
|
||||
)
|
||||
|
||||
MSCT_MAIN = (
|
||||
Musicreater,
|
||||
Musicreater.experiment,
|
||||
old_init,
|
||||
old_init.experiment,
|
||||
# Musicreater.previous,
|
||||
)
|
||||
|
||||
MSCT_PLUGIN = (Musicreater.plugin,)
|
||||
MSCT_PLUGIN = (old_init.old_plugin,)
|
||||
|
||||
MSCT_PLUGIN_FUNCTION = (
|
||||
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 Musicreater
|
||||
from Musicreater.plugin.addonpack import (
|
||||
import Musicreater.old_init as old_init
|
||||
from Musicreater.old_plugin.addonpack import (
|
||||
to_addon_pack_in_delay,
|
||||
to_addon_pack_in_repeater,
|
||||
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_repeater,
|
||||
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_path = input(f"请输入MIDI路径:")
|
||||
@@ -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:],
|
||||
)
|
||||
)
|
||||
@@ -1,9 +1,9 @@
|
||||
import Musicreater.experiment
|
||||
import Musicreater.plugin
|
||||
import Musicreater.plugin.mcstructfile
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.mcstructfile
|
||||
|
||||
print(
|
||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
|
||||
input("midi路径:"), old_exe_format=False
|
||||
),
|
||||
@@ -1,10 +1,10 @@
|
||||
import Musicreater
|
||||
import Musicreater.plugin
|
||||
import Musicreater.plugin.mcstructfile
|
||||
import Musicreater.old_init as old_init
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.mcstructfile
|
||||
|
||||
print(
|
||||
Musicreater.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"},
|
||||
@@ -1,15 +1,15 @@
|
||||
import Musicreater
|
||||
import Musicreater.plugin
|
||||
import Musicreater.plugin.websocket
|
||||
import Musicreater.old_init as old_init
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.websocket
|
||||
|
||||
import os
|
||||
|
||||
dire = input("midi目录:")
|
||||
|
||||
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
|
||||
)
|
||||
for names in os.listdir(
|
||||
@@ -19,6 +19,6 @@ print(
|
||||
],
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
@@ -1,6 +1,6 @@
|
||||
import Musicreater.experiment
|
||||
import Musicreater.plugin
|
||||
import Musicreater.plugin.mcstructfile
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.mcstructfile
|
||||
|
||||
msct = Musicreater.experiment.FutureMidiConvertKamiRES.from_midi_file(
|
||||
input("midi路径:"), old_exe_format=False
|
||||
@@ -24,7 +24,7 @@ for name in sorted(
|
||||
|
||||
print(
|
||||
"\n输出:",
|
||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
msct,
|
||||
opt,
|
||||
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||
@@ -1,6 +1,6 @@
|
||||
import Musicreater.experiment
|
||||
import Musicreater.plugin
|
||||
import Musicreater.plugin.mcstructfile
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.mcstructfile
|
||||
|
||||
msct = Musicreater.experiment.FutureMidiConvertLyricSupport.from_midi_file(
|
||||
input("midi路径:"), old_exe_format=False
|
||||
@@ -24,7 +24,7 @@ opt = input("输出路径:")
|
||||
|
||||
print(
|
||||
"\n输出:",
|
||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
msct,
|
||||
opt,
|
||||
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||
@@ -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)
|
||||
@@ -4,6 +4,8 @@
|
||||
requires-python = ">= 3.8, < 4.0"
|
||||
dependencies = [
|
||||
"mido >= 1.3",
|
||||
"tomli >= 2.4.0; python_version < '3.11'",
|
||||
"tomli-w >= 1.0.0",
|
||||
"xxhash >= 3",
|
||||
]
|
||||
|
||||
@@ -47,6 +49,7 @@
|
||||
full = [
|
||||
"TrimMCStruct <= 0.0.5.9",
|
||||
"brotli >= 1.0.0",
|
||||
"numpy",
|
||||
]
|
||||
dev = [
|
||||
"TrimMCStruct <= 0.0.5.9",
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -11,4 +11,4 @@
|
||||
不得用于商业用途
|
||||
若 音·创 库被用于商业用途,应当将其剔除
|
||||
|
||||
版权所有 © 2025 诸葛亮与八卦阵
|
||||
版权所有 © 2026 玉衡Alioth
|
||||
24
test_read.py
Normal file
24
test_read.py
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
# 一个简单的项目实践测试
|
||||
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")
|
||||
|
||||
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_2_music_plugin.load(Path("./resources/测试片段.mid"), None))
|
||||
# 我们建议用这种方式来代替
|
||||
t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(Path("./resources/测试片段.mid"), None)
|
||||
|
||||
print(_global_plugin_registry)
|
||||
print(msct._plugin_cache)
|
||||
|
||||
90
uv.lock
generated
90
uv.lock
generated
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.8, <4.0"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.10'",
|
||||
@@ -578,6 +578,9 @@ name = "musicreater"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "mido" },
|
||||
{ name = "tomli" },
|
||||
{ name = "tomli-w", version = "1.0.0", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.9'" },
|
||||
{ name = "tomli-w", version = "1.2.0", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version >= '3.9'" },
|
||||
{ name = "xxhash" },
|
||||
]
|
||||
|
||||
@@ -592,6 +595,9 @@ dev = [
|
||||
]
|
||||
full = [
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
@@ -601,8 +607,11 @@ requires-dist = [
|
||||
{ name = "brotli", marker = "extra == 'full'", specifier = ">=1.0.0" },
|
||||
{ name = "dill", marker = "extra == 'dev'" },
|
||||
{ name = "mido", specifier = ">=1.3" },
|
||||
{ name = "numpy", marker = "extra == 'full'" },
|
||||
{ name = "pyinstaller", marker = "extra == 'dev'" },
|
||||
{ name = "rich", marker = "extra == 'dev'" },
|
||||
{ name = "tomli" },
|
||||
{ name = "tomli-w" },
|
||||
{ name = "trimmcstruct", marker = "extra == 'dev'", specifier = "<=0.0.5.9" },
|
||||
{ name = "trimmcstruct", marker = "extra == 'full'", specifier = "<=0.0.5.9" },
|
||||
{ name = "twine", marker = "extra == 'dev'" },
|
||||
@@ -1041,6 +1050,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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "trimmcstruct"
|
||||
version = "0.0.5.9"
|
||||
|
||||
Reference in New Issue
Block a user