mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-17 06:08:00 +00:00
完成 Midi 导入插件移植
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 的插件基类,提供抽象接口以供实际插件使用
|
音·创 v3 的插件基类,提供抽象接口以供实际插件使用
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -42,6 +42,7 @@ from typing import (
|
|||||||
Iterator,
|
Iterator,
|
||||||
Set,
|
Set,
|
||||||
Type,
|
Type,
|
||||||
|
Mapping,
|
||||||
)
|
)
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
if sys.version_info >= (3, 11):
|
||||||
@@ -132,7 +133,7 @@ class PluginConfig(ABC):
|
|||||||
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: Dict[str, Any]) -> "PluginConfig":
|
def from_dict(cls, data: Mapping[str, Any]) -> "PluginConfig":
|
||||||
"""从字典创建配置实例
|
"""从字典创建配置实例
|
||||||
|
|
||||||
参数
|
参数
|
||||||
|
|||||||
32
Musicreater/_utils.py
Normal file
32
Musicreater/_utils.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
音·创 v3 的功能性内容合辑
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from copy import deepcopy, copy
|
||||||
|
from typing import Any, Dict, Generator, List, Optional, Tuple, Union, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
def enumerated_stuffcopy_dictionary(
|
||||||
|
enumeration_times: int = 17, staff: T = {}
|
||||||
|
) -> Dict[int, T]:
|
||||||
|
"""
|
||||||
|
生成一个字典,其中键从0到enumeration_times-1,值是staff的拷贝
|
||||||
|
"""
|
||||||
|
# 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
||||||
|
# 上面的这指的是 copy.deepcopy —— 金羿 来自 20260210
|
||||||
|
return {i: deepcopy(staff) for i in range(enumeration_times)}
|
||||||
@@ -592,6 +592,8 @@ MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
|||||||
|
|
||||||
# NoteBlockStudio “NBS”音色对照表
|
# NoteBlockStudio “NBS”音色对照表
|
||||||
# https://github.com/OpenNBS/NoteBlockStudio/blob/main/scripts/midi_instruments/midi_instruments.gml
|
# https://github.com/OpenNBS/NoteBlockStudio/blob/main/scripts/midi_instruments/midi_instruments.gml
|
||||||
|
# 此表来自于 Commit 1ab5357c197872495197f27ad8374d711b2a5195
|
||||||
|
# 需要更新:https://github.com/OpenNBS/NoteBlockStudio/compare/main...development?diff=unified&w
|
||||||
|
|
||||||
MM_NBS_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
MM_NBS_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
0: "note.harp",
|
0: "note.harp",
|
||||||
|
|||||||
69
Musicreater/builtin_plugins/midi_read/exceptions.py
Normal file
69
Musicreater/builtin_plugins/midi_read/exceptions.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件用到的一些报错类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles & YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from Musicreater.exceptions import MusicreaterOuterlyError
|
||||||
|
|
||||||
|
|
||||||
|
class MidiFormatError(MusicreaterOuterlyError):
|
||||||
|
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||||
|
super().__init__("MIDI 格式错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NotDefineTempoError(MidiFormatError):
|
||||||
|
"""没有Tempo设定导致时间无法计算的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""没有Tempo设定导致时间无法计算的错误"""
|
||||||
|
super().__init__("在曲目开始时没有声明 Tempo(未指定拍长):", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelOverFlowError(MidiFormatError):
|
||||||
|
"""一个midi中含有过多的通道"""
|
||||||
|
|
||||||
|
def __init__(self, max_channel=16, *args):
|
||||||
|
"""一个midi中含有过多的通道"""
|
||||||
|
super().__init__("含有过多的通道(数量应≤{}):".format(max_channel), *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NotDefineProgramError(MidiFormatError):
|
||||||
|
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||||
|
super().__init__("未指定演奏乐器:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NoteOnOffMismatchError(MidiFormatError):
|
||||||
|
"""音符开音和停止不匹配的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音符开音和停止不匹配的错误"""
|
||||||
|
super().__init__("音符不匹配:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class LyricMismatchError(MidiFormatError):
|
||||||
|
"""歌词匹配解析错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""有可能产生了错误的歌词解析"""
|
||||||
|
super().__init__("歌词解析错误:", *args)
|
||||||
@@ -19,10 +19,11 @@ Terms & Conditions: License.md in the root directory
|
|||||||
import mido
|
import mido
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import BinaryIO, Optional, Dict, List
|
from typing import BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping
|
||||||
|
|
||||||
from Musicreater import SingleMusic
|
from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos
|
||||||
from Musicreater.plugins import (
|
from Musicreater.plugins import (
|
||||||
music_input_plugin,
|
music_input_plugin,
|
||||||
PluginConfig,
|
PluginConfig,
|
||||||
@@ -30,10 +31,8 @@ from Musicreater.plugins import (
|
|||||||
PluginTypes,
|
PluginTypes,
|
||||||
MusicInputPluginBase,
|
MusicInputPluginBase,
|
||||||
)
|
)
|
||||||
from Musicreater.types import (
|
from Musicreater.exceptions import ZeroSpeedError, IllegalMinimumVolumeError
|
||||||
FittingFunctionType,
|
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
from .constants import (
|
from .constants import (
|
||||||
MIDI_DEFAULT_PROGRAM_VALUE,
|
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||||
@@ -41,26 +40,134 @@ from .constants import (
|
|||||||
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
)
|
)
|
||||||
from .utils import velocity_2_distance_natural, panning_2_rotation_trigonometric
|
from .exceptions import (
|
||||||
|
MidiFormatError,
|
||||||
|
NoteOnOffMismatchError,
|
||||||
|
ChannelOverFlowError,
|
||||||
|
LyricMismatchError,
|
||||||
|
)
|
||||||
|
from .utils import (
|
||||||
|
volume_2_distance_natural,
|
||||||
|
panning_2_rotation_trigonometric,
|
||||||
|
midi_msgs_to_noteinfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MidiImportConfig(PluginConfig):
|
class MidiImportConfig(PluginConfig):
|
||||||
"""Midi 音乐数据导入插件配置"""
|
"""Midi 音乐数据导入插件配置"""
|
||||||
|
|
||||||
ignore_mismatch_error: bool = True
|
# 系统设置
|
||||||
speed: float = 1.0
|
ignore_errors: bool = True
|
||||||
|
|
||||||
|
# 处理设置
|
||||||
|
speed_multiplier: float = 1.0
|
||||||
|
|
||||||
|
# 兼容不良 Midi 所定义的默认值
|
||||||
default_program_value: int = MIDI_DEFAULT_PROGRAM_VALUE
|
default_program_value: int = MIDI_DEFAULT_PROGRAM_VALUE
|
||||||
default_volume_value: int = MIDI_DEFAULT_VOLUME_VALUE
|
default_volume_value: int = MIDI_DEFAULT_VOLUME_VALUE
|
||||||
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO
|
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||||
pitched_note_rtable: Dict[int, str] = MM_TOUCH_PITCHED_INSTRUMENT_TABLE
|
|
||||||
percussion_note_rtable: Dict[int, str] = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE
|
# 对照表
|
||||||
vol_processing_function: FittingFunctionType = velocity_2_distance_natural
|
pitched_note_reference_table: Mapping[int, str] = None # type: ignore
|
||||||
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric
|
percussion_note_reference_table: Mapping[int, str] = None # type: ignore
|
||||||
note_rtable_replacement: Dict[str, str] = {}
|
note_replacement_table: Mapping[str, str] = None # type: ignore
|
||||||
|
|
||||||
|
# 参数转换函数
|
||||||
|
volume_process_function: Callable[[float], float] = volume_2_distance_natural
|
||||||
|
panning_processing_function: Callable[[float], float] = (
|
||||||
|
panning_2_rotation_trigonometric
|
||||||
|
)
|
||||||
|
|
||||||
|
# 分轨方式
|
||||||
|
divide_tracks_by_miditrack: bool = True
|
||||||
|
divide_tracks_by_midichannel: bool = False
|
||||||
|
divide_tracks_by_soundname: bool = True
|
||||||
|
divide_tracks_by_volume: bool = False
|
||||||
|
divide_tracks_by_panning: bool = False
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.pitched_note_reference_table = (
|
||||||
|
self.pitched_note_reference_table
|
||||||
|
if self.pitched_note_reference_table
|
||||||
|
else MM_TOUCH_PITCHED_INSTRUMENT_TABLE
|
||||||
|
)
|
||||||
|
self.percussion_note_reference_table = (
|
||||||
|
self.percussion_note_reference_table
|
||||||
|
if self.percussion_note_reference_table
|
||||||
|
else MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE
|
||||||
|
)
|
||||||
|
self.note_replacement_table = (
|
||||||
|
self.note_replacement_table if self.note_replacement_table else {}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@music_input_plugin("midi_2_music_by_tracks")
|
class ControlerKeys(Enum):
|
||||||
|
MIDI_PROGRAM = "midi_program"
|
||||||
|
MIDI_VOLUME = "midi_volume"
|
||||||
|
MIDI_PAN = "midi_pan"
|
||||||
|
|
||||||
|
|
||||||
|
class TrackDivisionDict(
|
||||||
|
Dict[
|
||||||
|
Tuple[
|
||||||
|
Optional[int],
|
||||||
|
Optional[int],
|
||||||
|
Optional[str],
|
||||||
|
Optional[float],
|
||||||
|
Optional[Tuple[float, float]],
|
||||||
|
],
|
||||||
|
SingleTrack,
|
||||||
|
]
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
音轨分轨字典
|
||||||
|
"""
|
||||||
|
|
||||||
|
division_by_miditrack: bool = True
|
||||||
|
division_by_midichannel: bool = False
|
||||||
|
division_by_soundname: bool = True
|
||||||
|
division_by_volume: bool = False
|
||||||
|
division_by_panning: bool = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args,
|
||||||
|
midi_import_config: MidiImportConfig = MidiImportConfig(),
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.division_by_miditrack = midi_import_config.divide_tracks_by_miditrack
|
||||||
|
self.division_by_midichannel = midi_import_config.divide_tracks_by_midichannel
|
||||||
|
self.division_by_soundname = midi_import_config.divide_tracks_by_soundname
|
||||||
|
self.division_by_volume = midi_import_config.divide_tracks_by_volume
|
||||||
|
self.division_by_panning = midi_import_config.divide_tracks_by_panning
|
||||||
|
|
||||||
|
def __getitem__(
|
||||||
|
self,
|
||||||
|
key: Tuple[
|
||||||
|
Optional[int],
|
||||||
|
Optional[int],
|
||||||
|
Optional[str],
|
||||||
|
Optional[float],
|
||||||
|
Optional[Tuple[float, float]],
|
||||||
|
],
|
||||||
|
) -> SingleTrack:
|
||||||
|
key = (
|
||||||
|
key[0] if self.division_by_miditrack else None,
|
||||||
|
key[1] if self.division_by_midichannel else None,
|
||||||
|
key[2] if self.division_by_soundname else None,
|
||||||
|
key[3] if self.division_by_volume else None,
|
||||||
|
key[4] if self.division_by_panning else None,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return super().__getitem__(key)
|
||||||
|
except KeyError:
|
||||||
|
self[key] = SingleTrack()
|
||||||
|
return self[key]
|
||||||
|
|
||||||
|
|
||||||
|
@music_input_plugin("midi_2_music_plugin")
|
||||||
class MidiImport2MusicPlugin(MusicInputPluginBase):
|
class MidiImport2MusicPlugin(MusicInputPluginBase):
|
||||||
"""Midi 音乐数据导入插件"""
|
"""Midi 音乐数据导入插件"""
|
||||||
|
|
||||||
@@ -76,14 +183,305 @@ class MidiImport2MusicPlugin(MusicInputPluginBase):
|
|||||||
supported_formats = ("MID", "MIDI")
|
supported_formats = ("MID", "MIDI")
|
||||||
|
|
||||||
def loadbytes(
|
def loadbytes(
|
||||||
self, bytes_buffer_in: BinaryIO, config: MidiImportConfig = MidiImportConfig()
|
self,
|
||||||
|
bytes_buffer_in: BinaryIO,
|
||||||
|
config: Optional[MidiImportConfig] = MidiImportConfig(),
|
||||||
) -> SingleMusic:
|
) -> SingleMusic:
|
||||||
midi_file = mido.MidiFile(file=bytes_buffer_in)
|
return self.midifile_2_singlemusic(
|
||||||
return SingleMusic() # =========================== TODO: 等待制作
|
mido.MidiFile(file=bytes_buffer_in),
|
||||||
|
config if config else MidiImportConfig(),
|
||||||
|
)
|
||||||
|
|
||||||
def load(
|
def load(
|
||||||
self, file_path: Path, config: MidiImportConfig = MidiImportConfig()
|
self, file_path: Path, config: Optional[MidiImportConfig] = MidiImportConfig()
|
||||||
) -> "SingleMusic":
|
) -> SingleMusic:
|
||||||
"""从 Midi 文件导入音乐数据"""
|
"""从 Midi 文件导入音乐数据"""
|
||||||
midi_file = mido.MidiFile(filename=file_path)
|
return self.midifile_2_singlemusic(
|
||||||
return SingleMusic() # =========================== TODO: 等待制作
|
mido.MidiFile(filename=file_path), config if config else MidiImportConfig()
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def midifile_2_singlemusic(
|
||||||
|
midi: mido.MidiFile,
|
||||||
|
config: MidiImportConfig = MidiImportConfig(),
|
||||||
|
) -> SingleMusic:
|
||||||
|
"""
|
||||||
|
将midi解析并转换为频道音符字典
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
midi: mido.MidiFile 对象
|
||||||
|
需要处理的midi对象
|
||||||
|
speed: float
|
||||||
|
音乐播放速度倍数
|
||||||
|
default_program_value: int
|
||||||
|
默认的 MIDI 乐器值
|
||||||
|
default_volume_value: int
|
||||||
|
默认的通道音量值
|
||||||
|
default_tempo_value: int
|
||||||
|
默认的 MIDI TEMPO 值
|
||||||
|
pitched_note_rtable: Dict[int, Tuple[str, int]]
|
||||||
|
乐音乐器Midi-MC对照表
|
||||||
|
percussion_note_rtable: Dict[int, Tuple[str, int]]
|
||||||
|
打击乐器Midi-MC对照表
|
||||||
|
vol_processing_function: Callable[[float], float]
|
||||||
|
音量对播放距离的拟合函数
|
||||||
|
pan_processing_function: Callable[[float], float]
|
||||||
|
声像偏移对播放旋转角度的拟合函数
|
||||||
|
note_rtable_replacement: Dict[str, str]
|
||||||
|
音符名称替换表,此表用于对 Minecraft 乐器名称进行替换,而非 Midi Program 的替换
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[SingleMusic, int, Dict[str, int]]
|
||||||
|
以通道作为分割的Midi音符列表字典, 音符总数, 乐器使用统计
|
||||||
|
"""
|
||||||
|
|
||||||
|
if config.speed_multiplier == 0:
|
||||||
|
raise ZeroSpeedError("播放速度不得为零,应为 (0,1] 范围内的实数。")
|
||||||
|
|
||||||
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
|
divided_tracks: TrackDivisionDict = TrackDivisionDict(midi_import_config=config)
|
||||||
|
|
||||||
|
value_controler_per_channel: Dict[int, Dict[ControlerKeys, int]] = (
|
||||||
|
enumerated_stuffcopy_dictionary(
|
||||||
|
staff={
|
||||||
|
ControlerKeys.MIDI_PROGRAM: config.default_program_value,
|
||||||
|
ControlerKeys.MIDI_VOLUME: config.default_volume_value,
|
||||||
|
ControlerKeys.MIDI_PAN: 64,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
midi_tempo = config.default_tempo_value
|
||||||
|
"""微秒每拍"""
|
||||||
|
note_count = 0
|
||||||
|
"""音符计数"""
|
||||||
|
note_count_per_instrument: Dict[str, int] = {}
|
||||||
|
"""乐器使用统计"""
|
||||||
|
microseconds = 0
|
||||||
|
"""当前的微妙时间"""
|
||||||
|
|
||||||
|
note_queue_A: Dict[int, List[Tuple[int, int]]] = (
|
||||||
|
enumerated_stuffcopy_dictionary(staff=[])
|
||||||
|
)
|
||||||
|
"""音符队列甲 Dict[通道, List[Tuple[int音高, int乐器, int轨道]]]"""
|
||||||
|
note_queue_B: Dict[int, List[Tuple[int, int, int]]] = (
|
||||||
|
enumerated_stuffcopy_dictionary(staff=[])
|
||||||
|
)
|
||||||
|
"""音符队列乙 Dict[通道, List[Tuple[int音高, int微秒时间]]]"""
|
||||||
|
|
||||||
|
midi_lyric_cache: List[Tuple[int, str]] = []
|
||||||
|
"""歌词缓存 List[Tuple[int微秒时间, str歌词内容]]"""
|
||||||
|
|
||||||
|
midi_text_list: List[str] = []
|
||||||
|
"""Midi 附加文本列表"""
|
||||||
|
midi_copyright_list: List[str] = []
|
||||||
|
"""Midi 版权列表"""
|
||||||
|
midi_track_name_dict: Dict[int, str] = {}
|
||||||
|
"""轨道名称字典 Dict[int轨道编号, str轨道名称]"""
|
||||||
|
|
||||||
|
for track_no, message_track in enumerate(midi.tracks):
|
||||||
|
for msg in message_track:
|
||||||
|
if msg.type == "set_tempo":
|
||||||
|
midi_tempo = msg.tempo
|
||||||
|
|
||||||
|
if msg.time != 0:
|
||||||
|
# 微秒
|
||||||
|
# 通常情况下,tempo 是 500000,tpb 在
|
||||||
|
microseconds += msg.time * midi_tempo / midi.ticks_per_beat
|
||||||
|
|
||||||
|
if msg.type == "program_change":
|
||||||
|
# 检测 乐器变化 之 midi 事件
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PROGRAM
|
||||||
|
] = msg.program
|
||||||
|
|
||||||
|
elif msg.is_cc(7):
|
||||||
|
# Control Change 更改当前通道的 音量 的事件(大幅度,最高有效位)
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_VOLUME
|
||||||
|
] = msg.value
|
||||||
|
elif msg.is_cc(10):
|
||||||
|
# Control Change 更改当前通道的 音调偏移 的事件(大幅度,最高有效位)
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PAN
|
||||||
|
] = msg.value
|
||||||
|
|
||||||
|
elif msg.type == "lyrics":
|
||||||
|
# 歌词事件
|
||||||
|
midi_lyric_cache.append((microseconds, msg.text))
|
||||||
|
# print(lyric_cache, flush=True)
|
||||||
|
elif msg.type == "text":
|
||||||
|
# 检测文本事件
|
||||||
|
midi_text_list.append(msg.text)
|
||||||
|
elif msg.type == "copyright":
|
||||||
|
# 检测版权事件
|
||||||
|
midi_copyright_list.append(msg.text)
|
||||||
|
elif msg.type == "track_name":
|
||||||
|
# 检测轨道名称事件
|
||||||
|
midi_track_name_dict[track_no] = msg.name
|
||||||
|
elif msg.type == "note_on" and msg.velocity != 0:
|
||||||
|
# 一个音符开始弹奏
|
||||||
|
|
||||||
|
# 加入音符队列甲(按通道分隔)
|
||||||
|
# (音高, 轨道)
|
||||||
|
note_queue_A[msg.channel].append((msg.note, track_no))
|
||||||
|
# 音符队列乙(按通道分隔)
|
||||||
|
# (乐器, 力度, 微秒)
|
||||||
|
note_queue_B[msg.channel].append(
|
||||||
|
(
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PROGRAM
|
||||||
|
],
|
||||||
|
msg.velocity,
|
||||||
|
microseconds,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif (msg.type == "note_off") or (
|
||||||
|
msg.type == "note_on" and msg.velocity == 0
|
||||||
|
):
|
||||||
|
# 一个音符结束弹奏
|
||||||
|
|
||||||
|
if (
|
||||||
|
msg.note,
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PROGRAM
|
||||||
|
],
|
||||||
|
) in note_queue_A[msg.channel]:
|
||||||
|
# 在甲队列中发现了同一个 音高和乐器且在同轨道 的音符
|
||||||
|
|
||||||
|
# 获取其音符力度和微秒数
|
||||||
|
_velocity, _program, _ms = note_queue_B[msg.channel][
|
||||||
|
note_queue_A[msg.channel].index((msg.note, track_no))
|
||||||
|
]
|
||||||
|
|
||||||
|
# 在队列中删除此音符
|
||||||
|
note_queue_A[msg.channel].remove((msg.note, track_no))
|
||||||
|
note_queue_B[msg.channel].remove((_velocity, _program, _ms))
|
||||||
|
|
||||||
|
_lyric = ""
|
||||||
|
# 找一找歌词吧
|
||||||
|
if midi_lyric_cache:
|
||||||
|
for i in range(len(midi_lyric_cache)):
|
||||||
|
if midi_lyric_cache[i][0] >= _ms:
|
||||||
|
_lyric = midi_lyric_cache.pop(i)[1]
|
||||||
|
break
|
||||||
|
|
||||||
|
# 更新结果信息
|
||||||
|
|
||||||
|
that_note, sound_name, orign_distance, sound_rotation = (
|
||||||
|
midi_msgs_to_noteinfo(
|
||||||
|
inst=(
|
||||||
|
msg.note
|
||||||
|
if (_is_percussion := (msg.channel == 9))
|
||||||
|
else _program
|
||||||
|
),
|
||||||
|
note=(_program if _is_percussion else msg.note),
|
||||||
|
percussive=_is_percussion,
|
||||||
|
volume=value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_VOLUME
|
||||||
|
],
|
||||||
|
velocity=_velocity,
|
||||||
|
panning=value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PAN
|
||||||
|
],
|
||||||
|
start_time=_ms, # 微秒
|
||||||
|
duration=microseconds - _ms, # 微秒
|
||||||
|
play_speed=config.speed_multiplier,
|
||||||
|
midi_reference_table=(
|
||||||
|
config.percussion_note_reference_table
|
||||||
|
if _is_percussion
|
||||||
|
else config.pitched_note_reference_table
|
||||||
|
),
|
||||||
|
volume_processing_method=config.volume_process_function,
|
||||||
|
panning_processing_method=config.panning_processing_function,
|
||||||
|
note_table_replacement=config.note_replacement_table,
|
||||||
|
lyric_line=_lyric,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
divided_tracks[
|
||||||
|
(
|
||||||
|
track_no,
|
||||||
|
msg.channel,
|
||||||
|
sound_name,
|
||||||
|
orign_distance,
|
||||||
|
sound_rotation,
|
||||||
|
)
|
||||||
|
].add(that_note)
|
||||||
|
|
||||||
|
# 更新统计信息
|
||||||
|
note_count += 1
|
||||||
|
if sound_name in note_count_per_instrument.keys():
|
||||||
|
note_count_per_instrument[sound_name] += 1
|
||||||
|
else:
|
||||||
|
note_count_per_instrument[sound_name] = 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 什么?找不到 note on 消息??
|
||||||
|
if config.ignore_errors:
|
||||||
|
print(
|
||||||
|
"[WARRING] MIDI格式错误 音符不匹配`{}`无法在上文`{}`中找到与之匹配的音符开音消息".format(
|
||||||
|
msg, note_queue_A[msg.channel]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise NoteOnOffMismatchError(
|
||||||
|
"当前的MIDI很可能有损坏之嫌……",
|
||||||
|
msg,
|
||||||
|
"无法在上文中找到与之匹配的音符开音消息。",
|
||||||
|
)
|
||||||
|
|
||||||
|
"""整合后的音乐通道格式
|
||||||
|
每个通道包括若干消息元素其中逃不过这三种:
|
||||||
|
|
||||||
|
1 切换乐器消息
|
||||||
|
("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
|
||||||
|
|
||||||
|
2 音符开始消息
|
||||||
|
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
|
||||||
|
|
||||||
|
3 音符结束消息
|
||||||
|
("NoteE", 结束的音符ID, 距离演奏开始的毫秒)"""
|
||||||
|
|
||||||
|
del midi_tempo
|
||||||
|
|
||||||
|
if midi_lyric_cache:
|
||||||
|
# 怎么有歌词多啊
|
||||||
|
if config.ignore_errors:
|
||||||
|
print(
|
||||||
|
"[WARRING] MIDI 解析错误 歌词对应错误,以下歌词未能填入音符之中,已经填入的仍可能有误 {}".format(
|
||||||
|
midi_lyric_cache
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise LyricMismatchError(
|
||||||
|
"MIDI 解析产生错误",
|
||||||
|
"歌词解析过程中无法对应音符,已填入的音符仍可能有误",
|
||||||
|
midi_lyric_cache,
|
||||||
|
)
|
||||||
|
|
||||||
|
final_music = SingleMusic(
|
||||||
|
credits="; ".join(midi_copyright_list),
|
||||||
|
extra_information={
|
||||||
|
"MIDI_TEXT_LIST": midi_text_list,
|
||||||
|
"NOTE_COUNT": note_count,
|
||||||
|
"NOTE_COUNT_PER_INSTRUMENT": note_count_per_instrument,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for track_properties, every_single_track in divided_tracks.items():
|
||||||
|
if track_properties[0] and (
|
||||||
|
track_name := midi_track_name_dict.get(track_properties[0])
|
||||||
|
):
|
||||||
|
every_single_track.track_name = track_name
|
||||||
|
if track_properties[2]:
|
||||||
|
every_single_track.track_instrument = track_properties[2]
|
||||||
|
if track_properties[3]:
|
||||||
|
every_single_track.sound_position.sound_distance = track_properties[3]
|
||||||
|
if track_properties[4]:
|
||||||
|
every_single_track.sound_position.sound_azimuth = track_properties[4]
|
||||||
|
final_music.append(every_single_track)
|
||||||
|
|
||||||
|
return final_music
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -20,18 +19,21 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Mapping
|
||||||
|
|
||||||
|
from Musicreater import SingleNote, SoundAtmos
|
||||||
|
|
||||||
|
|
||||||
def velocity_2_distance_natural(
|
def volume_2_distance_natural(
|
||||||
vol: float,
|
vol: float,
|
||||||
) -> float:
|
) -> float:
|
||||||
"""
|
"""
|
||||||
midi力度值拟合成的距离函数
|
Midi 力度值/音量值拟合成的距离函数,一种更加自然的听感?
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
vol: int
|
vol: int
|
||||||
midi 音符力度值
|
Midi 音符力度值(0~127)
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -50,20 +52,20 @@ def velocity_2_distance_natural(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def velocity_2_distance_straight(vol: float) -> float:
|
def volume_2_distance_straight(vol: float) -> float:
|
||||||
"""
|
"""
|
||||||
midi力度值拟合成的距离函数
|
Midi 力度值/音量值拟合成的距离函数,线性转换
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
vol: int
|
vol: int
|
||||||
midi 音符力度值
|
Midi 音符力度值(0~127)
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
float播放中心到玩家的距离
|
float播放中心到玩家的距离
|
||||||
"""
|
"""
|
||||||
return vol / -8 + 16
|
return (vol + 1) / -8 + 16
|
||||||
|
|
||||||
|
|
||||||
def panning_2_rotation_linear(pan_: float) -> float:
|
def panning_2_rotation_linear(pan_: float) -> float:
|
||||||
@@ -106,3 +108,115 @@ def panning_2_rotation_trigonometric(pan_: float) -> float:
|
|||||||
else:
|
else:
|
||||||
return math.degrees(math.acos((64 - pan_) / 63)) - 90
|
return math.degrees(math.acos((64 - pan_) / 63)) - 90
|
||||||
|
|
||||||
|
|
||||||
|
def midi_inst_to_mc_sound(
|
||||||
|
instrumentID: int,
|
||||||
|
reference_table: Mapping[int, str],
|
||||||
|
default_instrument: str = "note.flute",
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
返回midi的乐器ID对应的我的世界乐器名
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
instrumentID: int
|
||||||
|
midi的乐器ID
|
||||||
|
reference_table: Dict[int, Tuple[str, int]]
|
||||||
|
转换乐器参照表
|
||||||
|
default_instrument: str
|
||||||
|
查无此乐器时的替换乐器
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str我的世界乐器名
|
||||||
|
"""
|
||||||
|
return reference_table.get(
|
||||||
|
instrumentID,
|
||||||
|
default_instrument,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def midi_msgs_to_noteinfo(
|
||||||
|
inst: int, # 乐器编号
|
||||||
|
note: int,
|
||||||
|
percussive: bool, # 是否作为打击乐器启用
|
||||||
|
volume: int,
|
||||||
|
velocity: int,
|
||||||
|
panning: int,
|
||||||
|
start_time: int,
|
||||||
|
duration: int,
|
||||||
|
play_speed: float,
|
||||||
|
midi_reference_table: Mapping[int, str],
|
||||||
|
volume_processing_method: Callable[[float], float],
|
||||||
|
panning_processing_method: Callable[[float], float],
|
||||||
|
note_table_replacement: Mapping[str, str] = {},
|
||||||
|
lyric_line: str = "",
|
||||||
|
) -> Tuple[SingleNote, str, float, Tuple[float, float]]:
|
||||||
|
"""
|
||||||
|
将 Midi信息转为音符对象
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
inst: int
|
||||||
|
乐器编号
|
||||||
|
note: int
|
||||||
|
音高编号(音符编号)
|
||||||
|
percussive: bool
|
||||||
|
是否作为打击乐器启用
|
||||||
|
volume: int
|
||||||
|
音量
|
||||||
|
velocity: int
|
||||||
|
力度
|
||||||
|
panning: int
|
||||||
|
声相偏移
|
||||||
|
start_time: int
|
||||||
|
音符起始时间(微秒)
|
||||||
|
duration: int
|
||||||
|
音符持续时间(微秒)
|
||||||
|
play_speed: float
|
||||||
|
曲目播放速度
|
||||||
|
midi_reference_table: Dict[int, str]
|
||||||
|
转换对照表
|
||||||
|
volume_processing_method: Callable[[float], float]
|
||||||
|
音量处理函数
|
||||||
|
panning_processing_method: Callable[[float], float]
|
||||||
|
立体声相偏移处理函数
|
||||||
|
note_table_replacement: Dict[str, str]
|
||||||
|
音符替换表,定义 Minecraft 音符字串的替换
|
||||||
|
lyric_line: str
|
||||||
|
该音符的歌词
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
Tuple[
|
||||||
|
MineNote我的世界音符对象,
|
||||||
|
str我的世界声音名,
|
||||||
|
float播放中心到玩家的距离,
|
||||||
|
Tuple[float, float]声源旋转角度
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
mc_sound_ID = midi_inst_to_mc_sound(
|
||||||
|
inst,
|
||||||
|
midi_reference_table,
|
||||||
|
"note.bd" if percussive else "note.flute",
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
SingleNote(
|
||||||
|
midi_pitch=note,
|
||||||
|
note_volume=int((velocity / 127) + 0.5),
|
||||||
|
start_tick=(tk := int(start_time / float(play_speed) / 50000)),
|
||||||
|
keep_tick=round(duration / float(play_speed) / 50000),
|
||||||
|
mass_precision_time=round(
|
||||||
|
(start_time / float(play_speed) - tk * 50000) / 800
|
||||||
|
),
|
||||||
|
extra_information={
|
||||||
|
"LYRIC_TEXT": lyric_line,
|
||||||
|
"VOLUME_VALUE": volume,
|
||||||
|
"PIN_VALUE": panning,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
note_table_replacement.get(mc_sound_ID, mc_sound_ID),
|
||||||
|
volume_processing_method(volume),
|
||||||
|
(panning_processing_method(panning), 0),
|
||||||
|
)
|
||||||
|
|||||||
@@ -34,15 +34,6 @@ z = "z"
|
|||||||
z
|
z
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MIDI_PROGRAM = "program"
|
|
||||||
"""Midi乐器编号"""
|
|
||||||
|
|
||||||
MIDI_VOLUME = "volume"
|
|
||||||
"""Midi通道音量"""
|
|
||||||
|
|
||||||
MIDI_PAN = "pan"
|
|
||||||
"""Midi通道立体声场偏移"""
|
|
||||||
|
|
||||||
|
|
||||||
# Midi用对照表
|
# Midi用对照表
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 的内部数据类
|
音·创 v3 的内部数据类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -41,6 +41,7 @@ from typing import (
|
|||||||
Literal,
|
Literal,
|
||||||
Hashable,
|
Hashable,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
|
Mapping,
|
||||||
)
|
)
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
@@ -71,12 +72,14 @@ class SoundAtmos:
|
|||||||
------------
|
------------
|
||||||
distance: float
|
distance: float
|
||||||
发声源距离玩家的距离(半径 `r`)
|
发声源距离玩家的距离(半径 `r`)
|
||||||
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
|
注:距离越近,音量越高,默认为 0。此参数可以作为音轨的音量使用。
|
||||||
|
音量若默认为 +0,则此值当为 8;此值最小为 0.01,最大为 16。
|
||||||
azimuth: tuple[float, float]
|
azimuth: tuple[float, float]
|
||||||
声源方位
|
声源方位
|
||||||
注:此参数为tuple,包含两个元素,分别表示:
|
注:此参数为tuple,包含两个元素,分别表示:
|
||||||
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
||||||
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上
|
||||||
|
(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
|
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
|
||||||
@@ -131,8 +134,8 @@ class SingleNote:
|
|||||||
note_pitch: int
|
note_pitch: int
|
||||||
"""midi音高"""
|
"""midi音高"""
|
||||||
|
|
||||||
velocity: int
|
volume: int
|
||||||
"""力度"""
|
"""力度/播放响度 0~100 百分比"""
|
||||||
|
|
||||||
start_time: int
|
start_time: int
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
@@ -149,9 +152,9 @@ class SingleNote:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
midi_pitch: Optional[int],
|
midi_pitch: Optional[int],
|
||||||
midi_velocity: int,
|
note_volume: int,
|
||||||
start_time: int,
|
start_tick: int,
|
||||||
last_time: int,
|
keep_tick: int,
|
||||||
mass_precision_time: int = 0,
|
mass_precision_time: int = 0,
|
||||||
extra_information: Dict[str, Any] = {},
|
extra_information: Dict[str, Any] = {},
|
||||||
):
|
):
|
||||||
@@ -162,8 +165,8 @@ class SingleNote:
|
|||||||
------------
|
------------
|
||||||
midi_pitch: int
|
midi_pitch: int
|
||||||
midi音高
|
midi音高
|
||||||
midi_velocity: int
|
note_volume: int
|
||||||
midi响度(力度)
|
响度/力度(百分比, 0~100)
|
||||||
start_time: int
|
start_time: int
|
||||||
开始之时(命令刻)
|
开始之时(命令刻)
|
||||||
注:此处的时间是用从乐曲开始到当前的刻数
|
注:此处的时间是用从乐曲开始到当前的刻数
|
||||||
@@ -183,11 +186,11 @@ class SingleNote:
|
|||||||
|
|
||||||
self.note_pitch: int = 66 if midi_pitch is None else midi_pitch
|
self.note_pitch: int = 66 if midi_pitch is None else midi_pitch
|
||||||
"""midi音高"""
|
"""midi音高"""
|
||||||
self.velocity: int = midi_velocity
|
self.volume: int = note_volume
|
||||||
"""响度(力度)"""
|
"""响度(力度)"""
|
||||||
self.start_time: int = start_time
|
self.start_time: int = start_tick
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
self.duration: int = last_time
|
self.duration: int = keep_tick
|
||||||
"""音符持续时间 命令刻"""
|
"""音符持续时间 命令刻"""
|
||||||
self.high_precision_start_time: int = mass_precision_time
|
self.high_precision_start_time: int = mass_precision_time
|
||||||
"""高精度开始时间偏量 0.4 毫秒"""
|
"""高精度开始时间偏量 0.4 毫秒"""
|
||||||
@@ -201,15 +204,15 @@ class SingleNote:
|
|||||||
group_1 := int.from_bytes(code_buffer[:6], "big")
|
group_1 := int.from_bytes(code_buffer[:6], "big")
|
||||||
) & 0b11111111111111111
|
) & 0b11111111111111111
|
||||||
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
|
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
|
||||||
note_velocity_ = (group_1 := group_1 >> 17) & 0b1111111
|
note_volume_ = (group_1 := group_1 >> 17) & 0b1111111
|
||||||
note_pitch_ = (group_1 := group_1 >> 7) & 0b1111111
|
note_pitch_ = (group_1 := group_1 >> 7) & 0b1111111
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return cls(
|
return cls(
|
||||||
midi_pitch=note_pitch_,
|
midi_pitch=note_pitch_,
|
||||||
midi_velocity=note_velocity_,
|
note_volume=note_volume_,
|
||||||
start_time=start_tick_,
|
start_tick=start_tick_,
|
||||||
last_time=duration_,
|
keep_tick=duration_,
|
||||||
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
|
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -245,7 +248,7 @@ class SingleNote:
|
|||||||
# SingleNote 的字节码
|
# SingleNote 的字节码
|
||||||
|
|
||||||
# note_pitch 7 位 支持到 127
|
# note_pitch 7 位 支持到 127
|
||||||
# velocity 长度 7 位 支持到 127
|
# volume 长度 7 位 支持到 127
|
||||||
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
||||||
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
|
||||||
# 共 48 位 合 6 字节
|
# 共 48 位 合 6 字节
|
||||||
@@ -255,7 +258,7 @@ class SingleNote:
|
|||||||
return (
|
return (
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
((((self.note_pitch << 7) + self.velocity) << 17) + self.start_time)
|
((((self.note_pitch << 7) + self.volume) << 17) + self.start_time)
|
||||||
<< 17
|
<< 17
|
||||||
)
|
)
|
||||||
+ self.duration
|
+ self.duration
|
||||||
@@ -290,9 +293,9 @@ class SingleNote:
|
|||||||
return self.extra_info.get(key, default)
|
return self.extra_info.get(key, default)
|
||||||
|
|
||||||
def stringize(self, include_extra_data: bool = False) -> str:
|
def stringize(self, include_extra_data: bool = False) -> str:
|
||||||
return "TrackedNote(Pitch = {}, Velocity = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
|
return "TrackedNote(Pitch = {}, Volume = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
|
||||||
self.note_pitch,
|
self.note_pitch,
|
||||||
self.velocity,
|
self.volume,
|
||||||
self.start_time,
|
self.start_time,
|
||||||
self.duration,
|
self.duration,
|
||||||
self.high_precision_start_time,
|
self.high_precision_start_time,
|
||||||
@@ -308,7 +311,7 @@ class SingleNote:
|
|||||||
) -> Tuple[int, int, int, int, int]:
|
) -> Tuple[int, int, int, int, int]:
|
||||||
return (
|
return (
|
||||||
self.note_pitch,
|
self.note_pitch,
|
||||||
self.velocity,
|
self.volume,
|
||||||
self.start_time,
|
self.start_time,
|
||||||
self.duration,
|
self.duration,
|
||||||
self.high_precision_start_time,
|
self.high_precision_start_time,
|
||||||
@@ -317,39 +320,38 @@ class SingleNote:
|
|||||||
def __dict__(self):
|
def __dict__(self):
|
||||||
return {
|
return {
|
||||||
"Pitch": self.note_pitch,
|
"Pitch": self.note_pitch,
|
||||||
"Velocity": self.velocity,
|
"Volume": self.volume,
|
||||||
"StartTick": self.start_time,
|
"StartTick": self.start_time,
|
||||||
"Duration": self.duration,
|
"Duration": self.duration,
|
||||||
"TimeOffset": self.high_precision_start_time,
|
"TimeOffset": self.high_precision_start_time,
|
||||||
"ExtraData": self.extra_info,
|
"ExtraData": self.extra_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other: "SingleNote") -> bool:
|
||||||
"""比较两个音符是否具有相同的属性,不计附加信息"""
|
"""比较两个音符是否具有相同的属性,不计附加信息"""
|
||||||
if not isinstance(other, self.__class__):
|
if not isinstance(other, self.__class__):
|
||||||
return False
|
return False
|
||||||
return self.__tuple__() == other.__tuple__()
|
return self.__tuple__() == other.__tuple__()
|
||||||
|
|
||||||
def __lt__(self, other) -> bool:
|
def __lt__(self, other: "SingleNote") -> bool:
|
||||||
"""比较自己是否在开始时间上早于另一个音符"""
|
"""比较自己是否在开始时间上早于另一个音符"""
|
||||||
if self.start_time == other.start_tick:
|
if self.start_time == other.start_time:
|
||||||
return self.high_precision_start_time < other.high_precision_time
|
return self.high_precision_start_time < other.high_precision_start_time
|
||||||
else:
|
else:
|
||||||
return self.start_time < other.start_tick
|
return self.start_time < other.start_time
|
||||||
|
|
||||||
def __gt__(self, other) -> bool:
|
def __gt__(self, other: "SingleNote") -> bool:
|
||||||
"""比较自己是否在开始时间上晚于另一个音符"""
|
"""比较自己是否在开始时间上晚于另一个音符"""
|
||||||
if self.start_time == other.start_tick:
|
if self.start_time == other.start_time:
|
||||||
return self.high_precision_start_time > other.high_precision_time
|
return self.high_precision_start_time > other.high_precision_start_time
|
||||||
else:
|
else:
|
||||||
return self.start_time > other.start_tick
|
return self.start_time > other.start_time
|
||||||
|
|
||||||
|
|
||||||
class CurvableParam(str, Enum):
|
class CurvableParam(str, Enum):
|
||||||
"""可曲线化的参数枚举类"""
|
"""可曲线化的参数枚举类"""
|
||||||
|
|
||||||
PITCH = "adjust_note_pitch"
|
PITCH = "adjust_note_pitch"
|
||||||
VELOCITY = "adjust_note_velocity"
|
|
||||||
VOLUME = "adjust_note_volume"
|
VOLUME = "adjust_note_volume"
|
||||||
DISTANCE = "adjust_note_sound_distance"
|
DISTANCE = "adjust_note_sound_distance"
|
||||||
LR_PANNING = "adjust_note_leftright_panning_degree"
|
LR_PANNING = "adjust_note_leftright_panning_degree"
|
||||||
@@ -361,13 +363,11 @@ class MineNote:
|
|||||||
"""我的世界音符对象(仅提供我的世界相关接口)"""
|
"""我的世界音符对象(仅提供我的世界相关接口)"""
|
||||||
|
|
||||||
pitch: float
|
pitch: float
|
||||||
"""midi音高"""
|
"""Midi 音高"""
|
||||||
instrument: str
|
instrument: str
|
||||||
"""乐器ID"""
|
"""乐器 ID"""
|
||||||
velocity: float
|
|
||||||
"""力度"""
|
|
||||||
volume: float
|
volume: float
|
||||||
"""音量"""
|
"""力度/播放音量 0~100 百分比"""
|
||||||
start_tick: int
|
start_tick: int
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
duration_tick: int
|
duration_tick: int
|
||||||
@@ -384,12 +384,10 @@ class MineNote:
|
|||||||
cls,
|
cls,
|
||||||
note: SingleNote,
|
note: SingleNote,
|
||||||
note_instrument: str,
|
note_instrument: str,
|
||||||
sound_volume: float,
|
|
||||||
is_persiced_time: bool,
|
is_persiced_time: bool,
|
||||||
is_percussive_note: bool,
|
is_percussive_note: bool,
|
||||||
sound_position: SoundAtmos,
|
sound_position: SoundAtmos,
|
||||||
adjust_note_pitch: float = 0.0,
|
adjust_note_pitch: float = 0.0,
|
||||||
adjust_note_velocity: float = 0.0,
|
|
||||||
adjust_note_volume: float = 0.0,
|
adjust_note_volume: float = 0.0,
|
||||||
adjust_note_sound_distance: float = 0.0,
|
adjust_note_sound_distance: float = 0.0,
|
||||||
adjust_note_leftright_panning_degree: float = 0.0,
|
adjust_note_leftright_panning_degree: float = 0.0,
|
||||||
@@ -404,8 +402,7 @@ class MineNote:
|
|||||||
return cls(
|
return cls(
|
||||||
pitch=note.note_pitch + adjust_note_pitch,
|
pitch=note.note_pitch + adjust_note_pitch,
|
||||||
instrument=note_instrument,
|
instrument=note_instrument,
|
||||||
velocity=note.velocity + adjust_note_velocity,
|
volume=note.volume + adjust_note_volume,
|
||||||
volume=sound_volume + adjust_note_volume,
|
|
||||||
start_tick=note.start_time,
|
start_tick=note.start_time,
|
||||||
duration_tick=note.duration,
|
duration_tick=note.duration,
|
||||||
persiced_time=note.high_precision_start_time if is_persiced_time else 0,
|
persiced_time=note.high_precision_start_time if is_persiced_time else 0,
|
||||||
@@ -426,9 +423,6 @@ class SingleTrack(List[SingleNote]):
|
|||||||
track_instrument: str
|
track_instrument: str
|
||||||
"""乐器ID"""
|
"""乐器ID"""
|
||||||
|
|
||||||
track_volume: float
|
|
||||||
"""该音轨的音量"""
|
|
||||||
|
|
||||||
is_high_time_precision: bool
|
is_high_time_precision: bool
|
||||||
"""该音轨是否使用高精度时间"""
|
"""该音轨是否使用高精度时间"""
|
||||||
|
|
||||||
@@ -446,14 +440,13 @@ class SingleTrack(List[SingleNote]):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
*args: SingleNote,
|
||||||
name: str = "未命名音轨",
|
name: str = "未命名音轨",
|
||||||
instrument: str = "",
|
instrument: str = "",
|
||||||
volume: float = 0,
|
|
||||||
precise_time: bool = True,
|
precise_time: bool = True,
|
||||||
percussion: bool = False,
|
percussion: bool = False,
|
||||||
sound_direction: SoundAtmos = SoundAtmos(),
|
sound_direction: SoundAtmos = SoundAtmos(),
|
||||||
extra_information: Dict[str, Any] = {},
|
extra_information: Dict[str, Any] = {},
|
||||||
*args: SingleNote,
|
|
||||||
):
|
):
|
||||||
self.track_name = name
|
self.track_name = name
|
||||||
"""音轨名称"""
|
"""音轨名称"""
|
||||||
@@ -461,9 +454,6 @@ class SingleTrack(List[SingleNote]):
|
|||||||
self.track_instrument = instrument
|
self.track_instrument = instrument
|
||||||
"""乐器ID"""
|
"""乐器ID"""
|
||||||
|
|
||||||
self.track_volume = volume
|
|
||||||
"""音量"""
|
|
||||||
|
|
||||||
self.is_high_time_precision = precise_time
|
self.is_high_time_precision = precise_time
|
||||||
"""是否使用高精度时间"""
|
"""是否使用高精度时间"""
|
||||||
|
|
||||||
@@ -559,7 +549,6 @@ class SingleTrack(List[SingleNote]):
|
|||||||
yield MineNote.from_single_note(
|
yield MineNote.from_single_note(
|
||||||
note=_note,
|
note=_note,
|
||||||
note_instrument=self.track_instrument,
|
note_instrument=self.track_instrument,
|
||||||
sound_volume=self.track_volume,
|
|
||||||
is_persiced_time=self.is_high_time_precision,
|
is_persiced_time=self.is_high_time_precision,
|
||||||
is_percussive_note=self.is_percussive,
|
is_percussive_note=self.is_percussive,
|
||||||
sound_position=self.sound_position,
|
sound_position=self.sound_position,
|
||||||
@@ -626,12 +615,12 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
*args: SingleTrack,
|
||||||
name: str = "未命名乐曲",
|
name: str = "未命名乐曲",
|
||||||
creator: str = "未命名制作者",
|
creator: str = "未命名制作者",
|
||||||
original_author: str = "未命名原作者",
|
original_author: str = "未命名原作者",
|
||||||
description: str = "未命名简介",
|
description: str = "未命名简介",
|
||||||
credits: str = "未命名版权信息",
|
credits: str = "未命名版权信息",
|
||||||
*args: SingleTrack,
|
|
||||||
extra_information: Dict[str, Any] = {},
|
extra_information: Dict[str, Any] = {},
|
||||||
):
|
):
|
||||||
self.music_name = name
|
self.music_name = name
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 用到的一些报错类型
|
音·创 v3 用到的一些报错类型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -106,10 +106,10 @@ class OuterlyParameterError(MusicreaterOuterlyError):
|
|||||||
|
|
||||||
|
|
||||||
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
|
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
|
||||||
"""以0作为播放速度的错误"""
|
"""以 0 作为播放速度的错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""以0作为播放速度的错误"""
|
"""以 0 作为播放速度的错误"""
|
||||||
super().__init__("播放速度为零:", *args)
|
super().__init__("播放速度为零:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
|
|||||||
# Bug retreat! Bug retreat!
|
# Bug retreat! Bug retreat!
|
||||||
# Exceptions and errors are causing chaos
|
# Exceptions and errors are causing chaos
|
||||||
# Words combine! Codes unite!
|
# Words combine! Codes unite!
|
||||||
# Hurry to call the programmer! Let's Go!
|
# Hurry to call the Programmer! Let's Go!
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -96,9 +96,10 @@ class MusiCreater:
|
|||||||
def _get_plugin_within_iousage(
|
def _get_plugin_within_iousage(
|
||||||
get_func: Callable[[Union[Path, str]], Generator[T_IOPlugin, None, None]],
|
get_func: Callable[[Union[Path, str]], Generator[T_IOPlugin, None, None]],
|
||||||
fpath: Path,
|
fpath: Path,
|
||||||
plg_regdict: Dict[str, T_IOPlugin],
|
plg_regdict: Mapping[str, T_IOPlugin],
|
||||||
plg_id: Optional[str],
|
plg_id: Optional[str],
|
||||||
) -> T_IOPlugin:
|
) -> T_IOPlugin:
|
||||||
|
"""这个函数是用于从指定的注册表项里面调取实例的,仅供下面这几个函数使用"""
|
||||||
|
|
||||||
__plugin: Optional[T_IOPlugin] = None
|
__plugin: Optional[T_IOPlugin] = None
|
||||||
if plg_id:
|
if plg_id:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 内部数据使用的参数曲线
|
音·创 v3 内部数据使用的参数曲线
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -26,12 +26,10 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Any, List, Tuple
|
from typing import Optional, Any, List, Tuple, Callable
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import bisect
|
import bisect
|
||||||
|
|
||||||
from .types import FittingFunctionType
|
|
||||||
|
|
||||||
|
|
||||||
def _evaluate_bezier_segment(
|
def _evaluate_bezier_segment(
|
||||||
t0: float,
|
t0: float,
|
||||||
@@ -178,7 +176,7 @@ class Keyframe:
|
|||||||
value: float
|
value: float
|
||||||
|
|
||||||
# 函数插值模式
|
# 函数插值模式
|
||||||
out_interp: Optional[FittingFunctionType] = None
|
out_interp: Optional[Callable[[float], float]] = None
|
||||||
|
|
||||||
# 贝塞尔模式
|
# 贝塞尔模式
|
||||||
in_tangent: Optional[Tuple[float, float]] = (
|
in_tangent: Optional[Tuple[float, float]] = (
|
||||||
@@ -215,7 +213,7 @@ class ParamCurve:
|
|||||||
base_line: float = 0.0
|
base_line: float = 0.0
|
||||||
"""基线/默认值"""
|
"""基线/默认值"""
|
||||||
|
|
||||||
base_interpolation_function: FittingFunctionType
|
base_interpolation_function: Callable[[float], float]
|
||||||
"""默认(未指定区间时的)关键帧插值模式"""
|
"""默认(未指定区间时的)关键帧插值模式"""
|
||||||
|
|
||||||
boundary_behaviour: BoundaryBehaviour
|
boundary_behaviour: BoundaryBehaviour
|
||||||
@@ -227,7 +225,7 @@ class ParamCurve:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_value: float = 0.0,
|
base_value: float = 0.0,
|
||||||
default_interpolation_function: FittingFunctionType = InterpolationMethod.linear,
|
default_interpolation_function: Callable[[float], float] = InterpolationMethod.linear,
|
||||||
boundary_mode: BoundaryBehaviour = BoundaryBehaviour.CONSTANT,
|
boundary_mode: BoundaryBehaviour = BoundaryBehaviour.CONSTANT,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -257,7 +255,7 @@ class ParamCurve:
|
|||||||
self,
|
self,
|
||||||
time: float,
|
time: float,
|
||||||
value: float,
|
value: float,
|
||||||
out_interp: Optional[FittingFunctionType] = None,
|
out_interp: Optional[Callable[[float], float]] = None,
|
||||||
in_tangent: Optional[Tuple[float, float]] = None,
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
out_tangent: Optional[Tuple[float, float]] = None,
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
use_bezier: bool = False,
|
use_bezier: bool = False,
|
||||||
@@ -328,7 +326,7 @@ class ParamCurve:
|
|||||||
def update_key_interp(
|
def update_key_interp(
|
||||||
self,
|
self,
|
||||||
time: float,
|
time: float,
|
||||||
out_interp: Optional[FittingFunctionType] = None,
|
out_interp: Optional[Callable[[float], float]] = None,
|
||||||
in_tangent: Optional[Tuple[float, float]] = None,
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
out_tangent: Optional[Tuple[float, float]] = None,
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
use_bezier: bool = False,
|
use_bezier: bool = False,
|
||||||
@@ -486,7 +484,7 @@ class ParamCurve:
|
|||||||
"""返回 (time, value) 列表。"""
|
"""返回 (time, value) 列表。"""
|
||||||
return [(k.time, k.value) for k in self._keys]
|
return [(k.time, k.value) for k in self._keys]
|
||||||
|
|
||||||
def set_default_interpolation_function(self, interp_func: FittingFunctionType):
|
def set_default_interpolation_function(self, interp_func: Callable[[float], float]):
|
||||||
"""设置默认插值函数。"""
|
"""设置默认插值函数。"""
|
||||||
self.base_interpolation_function = interp_func
|
self.base_interpolation_function = interp_func
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 的插件接口与管理相关内容
|
音·创 v3 的插件接口与管理相关内容
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -213,7 +213,7 @@ class PluginRegistry:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_io_plugin_by_format(
|
def _get_io_plugin_by_format(
|
||||||
plugin_regdict: Dict[str, T_IOPlugin], fpath_or_format: Union[Path, str]
|
plugin_regdict: Mapping[str, T_IOPlugin], fpath_or_format: Union[Path, str]
|
||||||
) -> Generator[T_IOPlugin, None, None]:
|
) -> Generator[T_IOPlugin, None, None]:
|
||||||
if isinstance(fpath_or_format, str):
|
if isinstance(fpath_or_format, str):
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 定义的一些数据类型,可以用于类型检查器
|
音·创 v3 定义的一些数据类型,可以用于类型检查器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -19,7 +19,3 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
|
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
|
||||||
|
|
||||||
FittingFunctionType = Callable[[float], float]
|
|
||||||
"""
|
|
||||||
拟合函数类型
|
|
||||||
"""
|
|
||||||
|
|||||||
36
TO-DO.md
36
TO-DO.md
@@ -6,31 +6,31 @@
|
|||||||
1. 使用 `.MCT` 作为项目文件的后缀,然后考虑一下格式是否和之前的 MusicSequence 兼容,如果兼容的话可以照旧用 `.MSQ`,如不的话,可以试试想一个新的后缀名作为数据文件后缀
|
1. 使用 `.MCT` 作为项目文件的后缀,然后考虑一下格式是否和之前的 MusicSequence 兼容,如果兼容的话可以照旧用 `.MSQ`,如不的话,可以试试想一个新的后缀名作为数据文件后缀
|
||||||
2. 要求数据文件支持完全流式读入
|
2. 要求数据文件支持完全流式读入
|
||||||
|
|
||||||
- 音轨静音处理
|
- [] 音轨静音处理
|
||||||
当前没有处理
|
当前没有处理
|
||||||
|
|
||||||
- 优化音轨的存储方式
|
- [] 优化音轨的存储方式
|
||||||
当前是用列表,且每一次变动元素都要重新排序,这样消耗太大了,需要优化,改用最小堆形式(heapq)
|
当前是用列表,且每一次变动元素都要重新排序,这样消耗太大了,需要优化,改用最小堆形式(heapq)
|
||||||
|
|
||||||
- 移植 v2 功能到内置插件
|
- 移植 v2 功能到内置插件
|
||||||
目前 v2 的功能有很多,都要移植到 v3。
|
目前 v2 的功能有很多,都要移植到 v3。
|
||||||
1. 导入 Midi 文件到全曲
|
1. [x] 导入 Midi 文件到全曲
|
||||||
2. 导入 Midi 文件到指定轨道
|
2. [] 导入 Midi 文件到指定轨道
|
||||||
3. 导出到延迟播放器的结构文件(MCSTRUCTURE、BDX)
|
3. [] 导出到延迟播放器的结构文件(MCSTRUCTURE、BDX)
|
||||||
4. 导出到延迟播放器的附加包
|
4. [] 导出到延迟播放器的附加包
|
||||||
5. 导出到积分板播放器的以上两种形式
|
5. [] 导出到积分板播放器的以上两种形式
|
||||||
6. 导出到中继器播放器的以上两种形式
|
6. [] 导出到中继器播放器的以上两种形式
|
||||||
7. 在 WebSocket 播放器中播放
|
7. [] 在 WebSocket 播放器中播放
|
||||||
8. 导出到支持神羽资源包的以上 7 种形式
|
8. [] 导出到支持神羽资源包的以上 7 种形式
|
||||||
9. 对于 Midi 歌词的实验性功能
|
9. [] 对于 Midi 歌词的实验性功能
|
||||||
10. 对于 Java 版本适配的实验性功能
|
10. [] 对于 Java 版本适配的实验性功能
|
||||||
11. 对于听感优化的实验性功能(插值、偏移)
|
11. [] 对于听感优化的实验性功能(插值、偏移)
|
||||||
|
|
||||||
- 测试参数曲线的功能
|
- [] 测试参数曲线的功能
|
||||||
|
|
||||||
- 支持导出音符盒构成的音乐
|
- [] 支持导出音符盒构成的音乐
|
||||||
|
|
||||||
- 支持导出成 schematic 结构
|
- [] 支持导出成 schematic 结构
|
||||||
|
|
||||||
## 讨论
|
## 讨论
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
|
|
||||||
引入了插件惟一识别码之后当然是采用 `Dict[插件唯一识别码, 插件对象]` 来存储插件了~之前插件名称的内容是我想得太浅了,我写完所有代码之后才想到插件名称是中文还带空格的任意字符串……
|
引入了插件惟一识别码之后当然是采用 `Dict[插件唯一识别码, 插件对象]` 来存储插件了~之前插件名称的内容是我想得太浅了,我写完所有代码之后才想到插件名称是中文还带空格的任意字符串……
|
||||||
|
|
||||||
2. 服务插件到底该怎么写?总不能留着一个 PluginType.SERVICE 的插件一直空在那里吧……
|
2. [] 服务插件到底该怎么写?总不能留着一个 PluginType.SERVICE 的插件一直空在那里吧……
|
||||||
|
|
||||||
3. 插件依赖性的优化。目前没有处理各个插件依赖关系的问题,如果插件之间彼此依赖要怎么做?
|
3. [] 插件依赖性的优化。目前没有处理各个插件依赖关系的问题,如果插件之间彼此依赖要怎么做?
|
||||||
我的想法是,这个依赖的处理由调用端来完成。比如我们的 伶伦工作站 是以 音·创 为核心的一个可视化数字音频工作站。
|
我的想法是,这个依赖的处理由调用端来完成。比如我们的 伶伦工作站 是以 音·创 为核心的一个可视化数字音频工作站。
|
||||||
那么应该由伶伦来处理依赖关系并加载之。
|
那么应该由伶伦来处理依赖关系并加载之。
|
||||||
|
|||||||
237
docs/异常继承关系.mmd
Normal file
237
docs/异常继承关系.mmd
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
|
||||||
|
classDiagram
|
||||||
|
|
||||||
|
direction LR
|
||||||
|
|
||||||
|
class Exception{
|
||||||
|
Python 内置基类
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicreaterBaseException {
|
||||||
|
"[音·创] - ..."
|
||||||
|
所有音·创 v3 错误的基类
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicreaterInnerlyError {
|
||||||
|
"内部错误 - ..."
|
||||||
|
面向开发者的内部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicreaterOuterlyError {
|
||||||
|
"外部错误 - ..."
|
||||||
|
面向用户的外部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class InnerlyParameterError {
|
||||||
|
"内部传参错误 - ..."
|
||||||
|
内部参数相关错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class OuterlyParameterError {
|
||||||
|
"参数错误 - ..."
|
||||||
|
外部参数相关错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParameterTypeError {
|
||||||
|
"参数类型错误:..."
|
||||||
|
继承自 InnerlyParameterError 和 TypeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParameterValueError {
|
||||||
|
"参数数值错误:..."
|
||||||
|
继承自 InnerlyParameterError 和 ValueError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginNotSpecifiedError {
|
||||||
|
"未指定插件:..."
|
||||||
|
继承自 InnerlyParameterError 和 LookupError
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZeroSpeedError {
|
||||||
|
"播放速度为零:..."
|
||||||
|
继承自 OuterlyParameterError 和 ZeroDivisionError
|
||||||
|
}
|
||||||
|
|
||||||
|
class IllegalMinimumVolumeError {
|
||||||
|
"最小播放音量超出范围:..."
|
||||||
|
继承自 OuterlyParameterError 和 ValueError
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileFormatNotSupportedError {
|
||||||
|
"不支持的文件格式:..."
|
||||||
|
继承自 MusicreaterOuterlyError
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteBinaryDecodeError {
|
||||||
|
"解码音乐存储二进制数据时出现问题 - ..."
|
||||||
|
继承自 MusicreaterOuterlyError
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleNoteDecodeError {
|
||||||
|
"音符解码出错:..."
|
||||||
|
继承自 NoteBinaryDecodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteBinaryFileTypeError {
|
||||||
|
"无法识别音乐存储文件对应的类型:..."
|
||||||
|
继承自 NoteBinaryDecodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteBinaryFileVerificationFailed {
|
||||||
|
"音乐存储文件校验失败:..."
|
||||||
|
继承自 NoteBinaryDecodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginDefineError {
|
||||||
|
"插件内部错误 - ..."
|
||||||
|
插件定义相关的内部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginInstanceNotFoundError {
|
||||||
|
"插件实例未找到:..."
|
||||||
|
继承自 PluginDefineError 和 LookupError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginAttributeNotFoundError {
|
||||||
|
"插件类的必要属性不存在:..."
|
||||||
|
继承自 PluginDefineError 和 AttributeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoError {
|
||||||
|
"插件元信息定义错误 - ..."
|
||||||
|
插件元信息相关错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoTypeError {
|
||||||
|
"插件元信息类型错误:..."
|
||||||
|
继承自 PluginMetainfoError 和 TypeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoValueError {
|
||||||
|
"插件元信息数值错误:..."
|
||||||
|
继承自 PluginMetainfoError 和 ValueError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoNotFoundError {
|
||||||
|
"插件元信息未定义:..."
|
||||||
|
继承自 PluginMetainfoError 和 PluginAttributeNotFoundError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginLoadError {
|
||||||
|
"插件加载错误 - ..."
|
||||||
|
插件加载相关的外部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginNotFoundError {
|
||||||
|
"插件未找到:..."
|
||||||
|
继承自 PluginLoadError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginRegisteredError {
|
||||||
|
"插件重复注册:..."
|
||||||
|
继承自 PluginLoadError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginConfigRelatedError {
|
||||||
|
"插件配置相关错误 - ..."
|
||||||
|
插件配置相关错误基类
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginConfigLoadError {
|
||||||
|
"插件配置文件加载错误:..."
|
||||||
|
继承自 PluginLoadError、PluginConfigRelatedError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginConfigDumpError {
|
||||||
|
"插件配置文件保存错误:..."
|
||||||
|
继承自 PluginConfigRelatedError
|
||||||
|
}
|
||||||
|
%% 高亮定义
|
||||||
|
|
||||||
|
class ParameterTypeError ::: highlight
|
||||||
|
class ParameterValueError ::: highlight
|
||||||
|
class PluginNotSpecifiedError ::: highlight
|
||||||
|
class ZeroSpeedError ::: highlight
|
||||||
|
class IllegalMinimumVolumeError ::: highlight
|
||||||
|
class FileFormatNotSupportedError ::: highlight
|
||||||
|
class SingleNoteDecodeError ::: highlight
|
||||||
|
class NoteBinaryFileTypeError ::: highlight
|
||||||
|
class NoteBinaryFileVerificationFailed ::: highlight
|
||||||
|
class PluginInstanceNotFoundError ::: highlight
|
||||||
|
class PluginAttributeNotFoundError ::: highlight
|
||||||
|
class PluginMetainfoTypeError ::: highlight
|
||||||
|
class PluginMetainfoValueError ::: highlight
|
||||||
|
class PluginMetainfoNotFoundError ::: highlight
|
||||||
|
class PluginNotFoundError ::: highlight
|
||||||
|
class PluginRegisteredError ::: highlight
|
||||||
|
class PluginConfigLoadError ::: highlight
|
||||||
|
class PluginConfigDumpError ::: highlight
|
||||||
|
|
||||||
|
%% 定义高亮样式
|
||||||
|
classDef highlight fill:,stroke-width:5px
|
||||||
|
|
||||||
|
%% 继承关系(箭头从子类指向父类)
|
||||||
|
Exception <|-- MusicreaterBaseException
|
||||||
|
Exception <|-- TypeError
|
||||||
|
Exception <|-- ValueError
|
||||||
|
Exception <|-- LookupError
|
||||||
|
Exception <|-- AttributeError
|
||||||
|
Exception <|-- ZeroDivisionError
|
||||||
|
MusicreaterBaseException <|-- MusicreaterInnerlyError
|
||||||
|
MusicreaterBaseException <|-- MusicreaterOuterlyError
|
||||||
|
|
||||||
|
MusicreaterInnerlyError <|-- InnerlyParameterError
|
||||||
|
MusicreaterOuterlyError <|-- OuterlyParameterError
|
||||||
|
|
||||||
|
InnerlyParameterError <|-- ParameterTypeError
|
||||||
|
TypeError <|-- ParameterTypeError
|
||||||
|
|
||||||
|
InnerlyParameterError <|-- ParameterValueError
|
||||||
|
ValueError <|-- ParameterValueError
|
||||||
|
|
||||||
|
InnerlyParameterError <|-- PluginNotSpecifiedError
|
||||||
|
LookupError <|-- PluginNotSpecifiedError
|
||||||
|
|
||||||
|
OuterlyParameterError <|-- ZeroSpeedError
|
||||||
|
ZeroDivisionError <|-- ZeroSpeedError
|
||||||
|
|
||||||
|
OuterlyParameterError <|-- IllegalMinimumVolumeError
|
||||||
|
ValueError <|-- IllegalMinimumVolumeError
|
||||||
|
|
||||||
|
MusicreaterOuterlyError <|-- FileFormatNotSupportedError
|
||||||
|
MusicreaterOuterlyError <|-- NoteBinaryDecodeError
|
||||||
|
|
||||||
|
NoteBinaryDecodeError <|-- SingleNoteDecodeError
|
||||||
|
NoteBinaryDecodeError <|-- NoteBinaryFileTypeError
|
||||||
|
NoteBinaryDecodeError <|-- NoteBinaryFileVerificationFailed
|
||||||
|
|
||||||
|
MusicreaterInnerlyError <|-- PluginDefineError
|
||||||
|
|
||||||
|
PluginDefineError <|-- PluginInstanceNotFoundError
|
||||||
|
LookupError <|-- PluginInstanceNotFoundError
|
||||||
|
|
||||||
|
PluginDefineError <|-- PluginAttributeNotFoundError
|
||||||
|
AttributeError <|-- PluginAttributeNotFoundError
|
||||||
|
|
||||||
|
PluginDefineError <|-- PluginMetainfoError
|
||||||
|
|
||||||
|
PluginMetainfoError <|-- PluginMetainfoTypeError
|
||||||
|
TypeError <|-- PluginMetainfoTypeError
|
||||||
|
|
||||||
|
PluginMetainfoError <|-- PluginMetainfoValueError
|
||||||
|
ValueError <|-- PluginMetainfoValueError
|
||||||
|
|
||||||
|
PluginMetainfoError <|-- PluginMetainfoNotFoundError
|
||||||
|
PluginAttributeNotFoundError <|-- PluginMetainfoNotFoundError
|
||||||
|
|
||||||
|
MusicreaterOuterlyError <|-- PluginLoadError
|
||||||
|
|
||||||
|
PluginLoadError <|-- PluginNotFoundError
|
||||||
|
PluginLoadError <|-- PluginRegisteredError
|
||||||
|
|
||||||
|
MusicreaterOuterlyError <|-- PluginConfigRelatedError
|
||||||
|
|
||||||
|
PluginLoadError <|-- PluginConfigLoadError
|
||||||
|
PluginConfigRelatedError <|-- PluginConfigLoadError
|
||||||
|
|
||||||
|
PluginConfigRelatedError <|-- PluginConfigDumpError
|
||||||
67
docs/异常继承关系.svg
Normal file
67
docs/异常继承关系.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 652 KiB |
125
examples/doc_importdata_plugin.md
Normal file
125
examples/doc_importdata_plugin.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# 示例插件:导入音符数据
|
||||||
|
|
||||||
|
> 版权所有 © 2026 金羿
|
||||||
|
> Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
睿乐组织 开发交流群 [861684859](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=fxNYIX_zKMgaO8X6K7pP7tHtLB7JRvdX&noverify=0&group_code=861684859)
|
||||||
|
Email [TriM-Organization@hotmail.com](mailto:TriM-Organization@hotmail.com)
|
||||||
|
|
||||||
|
```license
|
||||||
|
本示例模块开放授权,同时,本教程文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新建文件夹 · 基础模块知识
|
||||||
|
|
||||||
|
|
||||||
|
首先,一个 **音·创 v3** 的插件应当存储于一个 Python 模块之中,也就是插件存在于可以被 import 语句引入的 module 中。
|
||||||
|
|
||||||
|
这就意味着,承载插件的模块本质上可以是多个 Python 的 `.py` 文件组成的,带有 `__init__.py` 的一个文件夹;
|
||||||
|
或者是一个简单的 `.py` 文件。
|
||||||
|
|
||||||
|
我们有这种共识:你已经知道了模块的相关知识,我后面无需赘述插件和模块的区别。
|
||||||
|
|
||||||
|
## 开始编写插件 · 插件基础
|
||||||
|
|
||||||
|
|
||||||
|
首先导入插件所需的类。
|
||||||
|
|
||||||
|
在这里我们是一个用来导入数据的插件。
|
||||||
|
|
||||||
|
所以就需要导入 `MusicInputPluginBase` 类和 `music_input_plugin` 函数。
|
||||||
|
|
||||||
|
同时,`PluginMetaInformation` 类和 `PluginTypes` 类也必须导入,这是插件的元信息所需要的。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
music_input_plugin,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
MusicInputPluginBase,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
如果插件需要配置,那么请再导入 `PluginConfig` 类,并从此继承一个类,且须用 dataclass 装饰器来注册之。
|
||||||
|
_对于这个类的使用方式,可以阅读 dataclass 的官方文档_
|
||||||
|
|
||||||
|
```python
|
||||||
|
from Musicreater.plugins import PluginConfig
|
||||||
|
from dataclasses import dataclass
|
||||||
|
@dataclass
|
||||||
|
class ExampleImportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 编写插件 · 开始
|
||||||
|
|
||||||
|
接着我们来制作一个插件。
|
||||||
|
|
||||||
|
首先,一个 **音·创 v3** 的插件应当是一个继承自我们已经准备好的插件基类的**类**(缩句:插件是类);
|
||||||
|
在 **音·创 v3** 中,任何对音乐的操作,包括导入、导出、处理,都分为对 **整首曲目** 的操作和对 **单个音轨** 的操作。
|
||||||
|
|
||||||
|
我们的样例是一个对**整首曲目**进行**导入操作**的插件,因此需要继承 `MusicInputPluginBase` 类。
|
||||||
|
插件类的类名称不得以 `Base` 结尾,因为咱写的是插件,不是插件基类。
|
||||||
|
|
||||||
|
在插件的类的开头,需要用插件注册装饰函数来对插件类装饰。
|
||||||
|
```python
|
||||||
|
@music_input_plugin("example_import_plugin")
|
||||||
|
class xxx:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
我们这里对应插件类型的注册器是 `music_input_plugin` 函数。
|
||||||
|
在注册器函数后的参数,是这个插件的惟一识别码。不应与其他插件混淆。
|
||||||
|
通常可以是这个插件的功能描述、或者就是插件名。
|
||||||
|
|
||||||
|
接着编写这个插件,也即是此类。
|
||||||
|
每个插件的类必须包含一个用于指定插件元信息的 `metainfo` 属性。
|
||||||
|
如果插件是导入数据或者导出数据的插件,则必须包含一个 `supported_formats` 属性,用以声明插件所支持的数据格式。
|
||||||
|
|
||||||
|
用于导入的插件类必须包含一个 `loadbytes` 方法,用于从字节流中导入数据。可选是否单独实现 `load` 方法,如果不单独实现,则在调用时,会直接通过打开文件后使用 `loadbytes` 的方式实现。
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 注册插件
|
||||||
|
@music_input_plugin("something_convert_to_music")
|
||||||
|
# 继承自对应类型的插件基类
|
||||||
|
class ExampleImportPlugin(MusicInputPluginBase):
|
||||||
|
|
||||||
|
# 插件元信息定义
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件", # 插件名称
|
||||||
|
author="金羿", # 插件作者
|
||||||
|
description="这是一个示例导入插件", # 插件描述
|
||||||
|
version=(0, 0, 1), # 插件版本
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT, # 插件类型
|
||||||
|
license="The Unlicense", # 插件许可证
|
||||||
|
dependencies=("something_convertion_library") # 插件对于其他插件的依赖项
|
||||||
|
)
|
||||||
|
|
||||||
|
# 导入导出插件支持的数据格式,大小写皆可
|
||||||
|
supported_formats = ("EXP", "example_format")
|
||||||
|
|
||||||
|
# 定义 loadbytes 方法,从字节流中导入数据
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: ExampleImportConfig
|
||||||
|
) -> "SingleMusic":
|
||||||
|
...
|
||||||
|
|
||||||
|
# 插件可选地定义 load 方法,从文件导入数据
|
||||||
|
def load(
|
||||||
|
self, file_path: Path, config: ExampleImportConfig
|
||||||
|
) -> "SingleMusic":
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
至此,一个插件的编写已经完成。
|
||||||
70
examples/exp_importdata_plugin.py
Normal file
70
examples/exp_importdata_plugin.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
示例插件:导入音符数据
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
本示例模块开放授权,本文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import BinaryIO
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
music_input_plugin,
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
MusicInputPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExampleImportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@music_input_plugin("something_convert_to_music")
|
||||||
|
class ExampleImportPlugin(MusicInputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导入插件",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
dependencies=("something_convertion_library")
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "example_format")
|
||||||
|
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: ExampleImportConfig
|
||||||
|
) -> "SingleMusic":
|
||||||
|
return SingleMusic()
|
||||||
|
|
||||||
|
def load(
|
||||||
|
self, file_path: Path, config: ExampleImportConfig
|
||||||
|
) -> "SingleMusic":
|
||||||
|
return SingleMusic()
|
||||||
@@ -182,10 +182,10 @@ class FutureMidiConvertKamiRES(MidiConvert):
|
|||||||
raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。")
|
raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。")
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
midi_channels: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
midi_channels: MineNoteChannelType = enumerated_stuff_copy(staff=[])
|
||||||
|
|
||||||
channel_controler: Dict[int, Dict[str, int]] = empty_midi_channels(
|
channel_controler: Dict[int, Dict[str, int]] = enumerated_stuff_copy(
|
||||||
default_staff={
|
staff={
|
||||||
MIDI_PROGRAM: default_program_value,
|
MIDI_PROGRAM: default_program_value,
|
||||||
MIDI_VOLUME: default_volume_value,
|
MIDI_VOLUME: default_volume_value,
|
||||||
MIDI_PAN: 64,
|
MIDI_PAN: 64,
|
||||||
@@ -205,7 +205,7 @@ class FutureMidiConvertKamiRES(MidiConvert):
|
|||||||
int,
|
int,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
] = empty_midi_channels(default_staff=[])
|
] = enumerated_stuff_copy(staff=[])
|
||||||
note_queue_B: Dict[
|
note_queue_B: Dict[
|
||||||
int,
|
int,
|
||||||
List[
|
List[
|
||||||
@@ -214,7 +214,7 @@ class FutureMidiConvertKamiRES(MidiConvert):
|
|||||||
int,
|
int,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
] = empty_midi_channels(default_staff=[])
|
] = enumerated_stuff_copy(staff=[])
|
||||||
|
|
||||||
# 直接使用mido.midifiles.tracks.merge_tracks转为单轨
|
# 直接使用mido.midifiles.tracks.merge_tracks转为单轨
|
||||||
# 采用的时遍历信息思路
|
# 采用的时遍历信息思路
|
||||||
@@ -1042,7 +1042,7 @@ class FutureMidiConvertM5(MidiConvert):
|
|||||||
# )
|
# )
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
midi_channels: ChannelType = empty_midi_channels()
|
midi_channels: ChannelType = enumerated_stuff_copy()
|
||||||
tempo = 500000
|
tempo = 500000
|
||||||
|
|
||||||
# 我们来用通道统计音乐信息
|
# 我们来用通道统计音乐信息
|
||||||
@@ -1052,7 +1052,7 @@ class FutureMidiConvertM5(MidiConvert):
|
|||||||
if not track:
|
if not track:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
note_queue = empty_midi_channels(default_staff=[])
|
note_queue = enumerated_stuff_copy(staff=[])
|
||||||
|
|
||||||
for msg in track:
|
for msg in track:
|
||||||
if msg.time != 0:
|
if msg.time != 0:
|
||||||
|
|||||||
@@ -34,14 +34,6 @@ class MSCTBaseException(Exception):
|
|||||||
raise self
|
raise self
|
||||||
|
|
||||||
|
|
||||||
class MidiFormatException(MSCTBaseException):
|
|
||||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
|
||||||
super().__init__("MIDI 格式错误", *args)
|
|
||||||
|
|
||||||
|
|
||||||
class MidiDestroyedError(MSCTBaseException):
|
class MidiDestroyedError(MSCTBaseException):
|
||||||
"""Midi文件损坏"""
|
"""Midi文件损坏"""
|
||||||
|
|
||||||
@@ -82,84 +74,3 @@ class CommandFormatError(MSCTBaseException, RuntimeError):
|
|||||||
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
||||||
|
|
||||||
|
|
||||||
class NotDefineTempoError(MidiFormatException):
|
|
||||||
"""没有Tempo设定导致时间无法计算的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""没有Tempo设定导致时间无法计算的错误"""
|
|
||||||
super().__init__("在曲目开始时没有声明 Tempo(未指定拍长)", *args)
|
|
||||||
|
|
||||||
|
|
||||||
class ChannelOverFlowError(MidiFormatException):
|
|
||||||
"""一个midi中含有过多的通道"""
|
|
||||||
|
|
||||||
def __init__(self, max_channel=16, *args):
|
|
||||||
"""一个midi中含有过多的通道"""
|
|
||||||
super().__init__("含有过多的通道(数量应≤{})".format(max_channel), *args)
|
|
||||||
|
|
||||||
|
|
||||||
class NotDefineProgramError(MidiFormatException):
|
|
||||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
|
||||||
super().__init__("未指定演奏乐器", *args)
|
|
||||||
|
|
||||||
|
|
||||||
class NoteOnOffMismatchError(MidiFormatException):
|
|
||||||
"""音符开音和停止不匹配的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""音符开音和停止不匹配的错误"""
|
|
||||||
super().__init__("音符不匹配", *args)
|
|
||||||
|
|
||||||
|
|
||||||
class LyricMismatchError(MSCTBaseException):
|
|
||||||
"""歌词匹配解析错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""有可能产生了错误的歌词解析"""
|
|
||||||
super().__init__("歌词解析错误", *args)
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
|
|
||||||
"""以0作为播放速度的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""以0作为播放速度的错误"""
|
|
||||||
super().__init__("播放速度为零", *args)
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class IllegalMinimumVolumeError(MSCTBaseException, ValueError):
|
|
||||||
"""最小播放音量有误的错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""最小播放音量错误"""
|
|
||||||
super().__init__("最小播放音量超出范围", *args)
|
|
||||||
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class MusicSequenceDecodeError(MSCTBaseException):
|
|
||||||
"""音乐序列解码错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""音乐序列无法正确解码的错误"""
|
|
||||||
super().__init__("解码音符序列文件时出现问题", *args)
|
|
||||||
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class MusicSequenceTypeError(MSCTBaseException):
|
|
||||||
"""音乐序列类型错误"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""无法识别音符序列字节码的类型"""
|
|
||||||
super().__init__("错误的音符序列字节类型", *args)
|
|
||||||
|
|
||||||
|
|
||||||
# 已重构
|
|
||||||
class MusicSequenceVerificationFailed(MusicSequenceDecodeError):
|
|
||||||
"""音乐序列校验失败"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
"""音符序列文件与其校验值不一致"""
|
|
||||||
super().__init__("音符序列文件校验失败", *args)
|
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ class MusicSequence:
|
|||||||
pitched_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
pitched_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
percussion_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
percussion_note_referance_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
minimum_vol: float = 0.1,
|
minimum_vol: float = 0.1,
|
||||||
volume_processing_function: FittingFunctionType = velocity_2_distance_natural,
|
volume_processing_function: FittingFunctionType = volume_2_distance_natural,
|
||||||
panning_processing_function: FittingFunctionType = panning_2_rotation_linear,
|
panning_processing_function: FittingFunctionType = panning_2_rotation_linear,
|
||||||
deviation: float = 0,
|
deviation: float = 0,
|
||||||
note_referance_table_replacement: Dict[str, str] = {},
|
note_referance_table_replacement: Dict[str, str] = {},
|
||||||
@@ -273,7 +273,7 @@ class MusicSequence:
|
|||||||
8 : (stt_index := 8 + (group_1 >> 10))
|
8 : (stt_index := 8 + (group_1 >> 10))
|
||||||
].decode("GB18030")
|
].decode("GB18030")
|
||||||
|
|
||||||
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
channels_: MineNoteChannelType = enumerated_stuff_copy(staff=[])
|
||||||
total_note_count = 0
|
total_note_count = 0
|
||||||
if verify:
|
if verify:
|
||||||
_header_index = stt_index
|
_header_index = stt_index
|
||||||
@@ -415,7 +415,7 @@ class MusicSequence:
|
|||||||
_t6_buffer = _t2_buffer = 0
|
_t6_buffer = _t2_buffer = 0
|
||||||
|
|
||||||
_channel_inst_chart: Dict[str, Dict[str, int]] = {}
|
_channel_inst_chart: Dict[str, Dict[str, int]] = {}
|
||||||
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
channels_: MineNoteChannelType = enumerated_stuff_copy(staff=[])
|
||||||
|
|
||||||
for i in range(total_note_count):
|
for i in range(total_note_count):
|
||||||
if verify:
|
if verify:
|
||||||
@@ -525,7 +525,7 @@ class MusicSequence:
|
|||||||
music_name_ = bytes_buffer_in[
|
music_name_ = bytes_buffer_in[
|
||||||
8 : (stt_index := 8 + (group_1 >> 10))
|
8 : (stt_index := 8 + (group_1 >> 10))
|
||||||
].decode("GB18030")
|
].decode("GB18030")
|
||||||
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
channels_: MineNoteChannelType = enumerated_stuff_copy(staff=[])
|
||||||
for channel_index in channels_.keys():
|
for channel_index in channels_.keys():
|
||||||
for i in range(
|
for i in range(
|
||||||
int.from_bytes(
|
int.from_bytes(
|
||||||
@@ -568,7 +568,7 @@ class MusicSequence:
|
|||||||
music_name_ = bytes_buffer_in[
|
music_name_ = bytes_buffer_in[
|
||||||
8 : (stt_index := 8 + (group_1 >> 10))
|
8 : (stt_index := 8 + (group_1 >> 10))
|
||||||
].decode("utf-8")
|
].decode("utf-8")
|
||||||
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
channels_: MineNoteChannelType = enumerated_stuff_copy(staff=[])
|
||||||
for channel_index in channels_.keys():
|
for channel_index in channels_.keys():
|
||||||
for i in range(
|
for i in range(
|
||||||
int.from_bytes(
|
int.from_bytes(
|
||||||
@@ -820,7 +820,7 @@ class MusicSequence:
|
|||||||
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||||
pitched_note_rtable: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
pitched_note_rtable: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
vol_processing_function: FittingFunctionType = velocity_2_distance_natural,
|
vol_processing_function: FittingFunctionType = volume_2_distance_natural,
|
||||||
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
|
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
|
||||||
note_rtable_replacement: Dict[str, str] = {},
|
note_rtable_replacement: Dict[str, str] = {},
|
||||||
) -> Tuple[MineNoteChannelType, int, Dict[str, int]]:
|
) -> Tuple[MineNoteChannelType, int, Dict[str, int]]:
|
||||||
@@ -860,10 +860,10 @@ class MusicSequence:
|
|||||||
raise ZeroSpeedError("播放速度不得为零,应为 (0,1] 范围内的实数。")
|
raise ZeroSpeedError("播放速度不得为零,应为 (0,1] 范围内的实数。")
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
midi_channels: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
midi_channels: MineNoteChannelType = enumerated_stuff_copy(staff=[])
|
||||||
|
|
||||||
channel_controler: Dict[int, Dict[str, int]] = empty_midi_channels(
|
channel_controler: Dict[int, Dict[str, int]] = enumerated_stuff_copy(
|
||||||
default_staff={
|
staff={
|
||||||
MIDI_PROGRAM: default_program_value,
|
MIDI_PROGRAM: default_program_value,
|
||||||
MIDI_VOLUME: default_volume_value,
|
MIDI_VOLUME: default_volume_value,
|
||||||
MIDI_PAN: 64,
|
MIDI_PAN: 64,
|
||||||
@@ -883,7 +883,7 @@ class MusicSequence:
|
|||||||
int,
|
int,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
] = empty_midi_channels(default_staff=[])
|
] = enumerated_stuff_copy(staff=[])
|
||||||
note_queue_B: Dict[
|
note_queue_B: Dict[
|
||||||
int,
|
int,
|
||||||
List[
|
List[
|
||||||
@@ -892,7 +892,7 @@ class MusicSequence:
|
|||||||
int,
|
int,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
] = empty_midi_channels(default_staff=[])
|
] = enumerated_stuff_copy(staff=[])
|
||||||
|
|
||||||
lyric_cache: List[Tuple[int, str]] = []
|
lyric_cache: List[Tuple[int, str]] = []
|
||||||
|
|
||||||
@@ -1099,7 +1099,7 @@ class MidiConvert(MusicSequence):
|
|||||||
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
enable_old_exe_format: bool = False,
|
enable_old_exe_format: bool = False,
|
||||||
minimum_volume: float = 0.1,
|
minimum_volume: float = 0.1,
|
||||||
vol_processing_function: FittingFunctionType = velocity_2_distance_natural,
|
vol_processing_function: FittingFunctionType = volume_2_distance_natural,
|
||||||
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
|
pan_processing_function: FittingFunctionType = panning_2_rotation_trigonometric,
|
||||||
pitch_deviation: float = 0,
|
pitch_deviation: float = 0,
|
||||||
note_rtable_replacement: Dict[str, str] = {},
|
note_rtable_replacement: Dict[str, str] = {},
|
||||||
@@ -1182,7 +1182,7 @@ class MidiConvert(MusicSequence):
|
|||||||
percussion_note_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
percussion_note_table: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
old_exe_format: bool = False,
|
old_exe_format: bool = False,
|
||||||
min_volume: float = 0.1,
|
min_volume: float = 0.1,
|
||||||
vol_processing_func: FittingFunctionType = velocity_2_distance_natural,
|
vol_processing_func: FittingFunctionType = volume_2_distance_natural,
|
||||||
pan_processing_func: FittingFunctionType = panning_2_rotation_linear,
|
pan_processing_func: FittingFunctionType = panning_2_rotation_linear,
|
||||||
music_pitch_deviation: float = 0,
|
music_pitch_deviation: float = 0,
|
||||||
note_table_replacement: Dict[str, str] = {},
|
note_table_replacement: Dict[str, str] = {},
|
||||||
|
|||||||
@@ -39,31 +39,15 @@ from Musicreater.constants import (
|
|||||||
MM_INSTRUMENT_DEVIATION_TABLE,
|
MM_INSTRUMENT_DEVIATION_TABLE,
|
||||||
MM_INSTRUMENT_RANGE_TABLE,
|
MM_INSTRUMENT_RANGE_TABLE,
|
||||||
)
|
)
|
||||||
from .old_exceptions import MusicSequenceDecodeError
|
from Musicreater.exceptions import SingleNoteDecodeError
|
||||||
|
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.midi_read.utils import midi_inst_to_mc_sound
|
||||||
|
|
||||||
from .subclass import MineNote, mctick2timestr, SingleNoteBox
|
from .subclass import MineNote, mctick2timestr, SingleNoteBox
|
||||||
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
||||||
|
|
||||||
|
|
||||||
def empty_midi_channels(
|
|
||||||
channel_count: int = 17, default_staff: Any = {}
|
|
||||||
) -> Dict[int, Any]:
|
|
||||||
"""
|
|
||||||
空MIDI通道字典
|
|
||||||
"""
|
|
||||||
|
|
||||||
return dict(
|
|
||||||
(
|
|
||||||
i,
|
|
||||||
(
|
|
||||||
default_staff.copy()
|
|
||||||
if isinstance(default_staff, (dict, list))
|
|
||||||
else default_staff
|
|
||||||
),
|
|
||||||
) # 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
|
||||||
for i in range(channel_count)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def inst_to_sould_with_deviation(
|
def inst_to_sould_with_deviation(
|
||||||
instrumentID: int,
|
instrumentID: int,
|
||||||
reference_table: MidiInstrumentTableType,
|
reference_table: MidiInstrumentTableType,
|
||||||
@@ -99,34 +83,6 @@ def inst_to_sould_with_deviation(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def midi_inst_to_mc_sound(
|
|
||||||
instrumentID: int,
|
|
||||||
reference_table: MidiInstrumentTableType,
|
|
||||||
default_instrument: str = "note.flute",
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
返回midi的乐器ID对应的我的世界乐器名
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
instrumentID: int
|
|
||||||
midi的乐器ID
|
|
||||||
reference_table: Dict[int, Tuple[str, int]]
|
|
||||||
转换乐器参照表
|
|
||||||
default_instrument: str
|
|
||||||
查无此乐器时的替换乐器
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
str我的世界乐器名
|
|
||||||
"""
|
|
||||||
return reference_table.get(
|
|
||||||
instrumentID,
|
|
||||||
default_instrument,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def minenote_to_command_parameters(
|
def minenote_to_command_parameters(
|
||||||
mine_note: MineNote,
|
mine_note: MineNote,
|
||||||
pitch_deviation: float = 0,
|
pitch_deviation: float = 0,
|
||||||
@@ -175,85 +131,6 @@ def minenote_to_command_parameters(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def midi_msgs_to_minenote(
|
|
||||||
inst_: int, # 乐器编号
|
|
||||||
note_: int,
|
|
||||||
percussive_: bool, # 是否作为打击乐器启用
|
|
||||||
volume_: int,
|
|
||||||
velocity_: int,
|
|
||||||
panning_: int,
|
|
||||||
start_time_: int,
|
|
||||||
duration_: int,
|
|
||||||
play_speed: float,
|
|
||||||
midi_reference_table: MidiInstrumentTableType,
|
|
||||||
volume_processing_method_: FittingFunctionType,
|
|
||||||
panning_processing_method_: FittingFunctionType,
|
|
||||||
note_table_replacement: Dict[str, str] = {},
|
|
||||||
lyric_line: str = "",
|
|
||||||
) -> MineNote:
|
|
||||||
"""
|
|
||||||
将Midi信息转为我的世界音符对象
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
------------
|
|
||||||
inst_: int
|
|
||||||
乐器编号
|
|
||||||
note_: int
|
|
||||||
音高编号(音符编号)
|
|
||||||
percussive_: bool
|
|
||||||
是否作为打击乐器启用
|
|
||||||
volume_: int
|
|
||||||
音量
|
|
||||||
velocity_: int
|
|
||||||
力度
|
|
||||||
panning_: int
|
|
||||||
声相偏移
|
|
||||||
start_time_: int
|
|
||||||
音符起始时间(微秒)
|
|
||||||
duration_: int
|
|
||||||
音符持续时间(微秒)
|
|
||||||
play_speed: float
|
|
||||||
曲目播放速度
|
|
||||||
midi_reference_table: Dict[int, str]
|
|
||||||
转换对照表
|
|
||||||
volume_processing_method_: Callable[[float], float]
|
|
||||||
音量处理函数
|
|
||||||
panning_processing_method_: Callable[[float], float]
|
|
||||||
立体声相偏移处理函数
|
|
||||||
note_table_replacement: Dict[str, str]
|
|
||||||
音符替换表,定义 Minecraft 音符字串的替换
|
|
||||||
lyric_line: str
|
|
||||||
该音符的歌词
|
|
||||||
|
|
||||||
Returns
|
|
||||||
---------
|
|
||||||
MineNote
|
|
||||||
我的世界音符对象
|
|
||||||
"""
|
|
||||||
mc_sound_ID = midi_inst_to_mc_sound(
|
|
||||||
inst_,
|
|
||||||
midi_reference_table,
|
|
||||||
"note.bd" if percussive_ else "note.flute",
|
|
||||||
)
|
|
||||||
|
|
||||||
return MineNote(
|
|
||||||
mc_sound_name=note_table_replacement.get(mc_sound_ID, mc_sound_ID),
|
|
||||||
midi_pitch=note_,
|
|
||||||
midi_velocity=velocity_,
|
|
||||||
start_time=(tk := int(start_time_ / float(play_speed) / 50000)),
|
|
||||||
last_time=round(duration_ / float(play_speed) / 50000),
|
|
||||||
mass_precision_time=round((start_time_ / float(play_speed) - tk * 50000) / 800),
|
|
||||||
is_percussion=percussive_,
|
|
||||||
distance=volume_processing_method_(volume_),
|
|
||||||
azimuth=(panning_processing_method_(panning_), 0),
|
|
||||||
extra_information={
|
|
||||||
"LYRIC_TEXT": lyric_line,
|
|
||||||
"VOLUME_VALUE": volume_,
|
|
||||||
"PIN_VALUE": panning_,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def midi_msgs_to_minenote_using_kami_respack(
|
def midi_msgs_to_minenote_using_kami_respack(
|
||||||
inst_: int, # 乐器编号
|
inst_: int, # 乐器编号
|
||||||
note_: int,
|
note_: int,
|
||||||
@@ -549,11 +426,10 @@ def load_decode_fsq_flush_release(
|
|||||||
)
|
)
|
||||||
except Exception as _err:
|
except Exception as _err:
|
||||||
# print(bytes_buffer_in[stt_index:end_index])
|
# print(bytes_buffer_in[stt_index:end_index])
|
||||||
raise MusicSequenceDecodeError(
|
raise SingleNoteDecodeError(
|
||||||
_err,
|
|
||||||
"所截取的音符码之首个字节:",
|
"所截取的音符码之首个字节:",
|
||||||
_first_byte,
|
_first_byte,
|
||||||
)
|
) from _err
|
||||||
|
|
||||||
|
|
||||||
def load_decode_msq_flush_release(
|
def load_decode_msq_flush_release(
|
||||||
@@ -600,8 +476,8 @@ def load_decode_msq_flush_release(
|
|||||||
|
|
||||||
_total_note_count = 1
|
_total_note_count = 1
|
||||||
|
|
||||||
_channel_infos = empty_midi_channels(
|
_channel_infos = enumerated_stuffcopy_dictionary(
|
||||||
default_staff={"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
|
staff={"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
|
||||||
)
|
)
|
||||||
|
|
||||||
for __channel_index in _channel_infos.keys():
|
for __channel_index in _channel_infos.keys():
|
||||||
@@ -730,7 +606,7 @@ def load_decode_msq_flush_release(
|
|||||||
_total_note_count -= 1
|
_total_note_count -= 1
|
||||||
except Exception as _err:
|
except Exception as _err:
|
||||||
# print(channels_)
|
# print(channels_)
|
||||||
raise MusicSequenceDecodeError("难以定位的解码错误", _err)
|
raise SingleNoteDecodeError("难以定位的解码错误") from _err
|
||||||
if not _read_in_note_list:
|
if not _read_in_note_list:
|
||||||
break
|
break
|
||||||
# _note_list.append
|
# _note_list.append
|
||||||
@@ -21,7 +21,7 @@ print(t := msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), N
|
|||||||
t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(
|
t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(
|
||||||
Path("./resources/测试片段.mid"),
|
Path("./resources/测试片段.mid"),
|
||||||
MidiImportConfig(
|
MidiImportConfig(
|
||||||
speed=1.0,
|
speed_multiplier=1.0,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
# 或者
|
# 或者
|
||||||
|
|||||||
Reference in New Issue
Block a user