重构么?不重构吧……

This commit is contained in:
2025-10-22 11:50:04 +08:00
parent 24742a140c
commit 63b59442a4

387
Musicreater/data.py Normal file
View File

@@ -0,0 +1,387 @@
# -*- coding: utf-8 -*-
"""
存储音·创新数据存储类
"""
# WARNING 本文件中使用之功能尚未启用
"""
版权所有 © 2025 金羿
Copyright © 2025 Eilles
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from math import sin, cos, asin, radians, degrees, sqrt, atan
from dataclasses import dataclass
from typing import Optional, Any, List, Tuple, Union, Dict, Sequence
from .constants import MC_PITCHED_INSTRUMENT_LIST
class SoundAtmos:
sound_distance: float
"""声源距离 方块"""
sound_azimuth: Tuple[float, float]
"""声源方位 角度"""
def __init__(
self,
distance: Optional[float] = None,
azimuth: Optional[Tuple[float, float]] = None,
) -> None:
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
"""声源方位"""
# 如果指定为零,那么为零,但如果不指定或者指定为负数,则为 0.01 的距离
self.sound_distance = (
(16 if distance > 16 else (distance if distance >= 0 else 0.01))
if distance is not None
else 0.01
)
"""声源距离"""
@classmethod
def from_displacement(
cls,
displacement: Optional[Tuple[float, float, float]] = None,
) -> "SoundAtmos":
if displacement is None:
# displacement = (0, 0, 0)
return cls()
else:
r = sqrt(displacement[0] ** 2 + displacement[1] ** 2 + displacement[2] ** 2)
if r == 0:
return cls(distance=0, azimuth=(0, 0))
else:
beta_h = round(degrees(asin(displacement[1] / r)), 8)
if displacement[2] == 0:
alpha_v = -90 if displacement[0] > 0 else 90
else:
alpha_v = round(
degrees(atan(-displacement[0] / displacement[2])), 8
)
return cls(distance=r, azimuth=(alpha_v, beta_h))
@property
def position_displacement(self) -> Tuple[float, float, float]:
"""声像位移"""
dk1 = self.sound_distance * round(cos(radians(self.sound_azimuth[1])), 8)
return (
-dk1 * round(sin(radians(self.sound_azimuth[0])), 8),
self.sound_distance * round(sin(radians(self.sound_azimuth[1])), 8),
dk1 * round(cos(radians(self.sound_azimuth[0])), 8),
)
@dataclass(init=False)
class SingleNote:
"""存储单个音符的类"""
note_pitch: int
"""midi音高"""
velocity: int
"""力度"""
start_tick: int
"""开始之时 命令刻"""
duration: int
"""音符持续时间 命令刻"""
high_precision_time: int
"""高精度开始时间偏量 1/1250 秒"""
extra_info: Dict[str, Any]
"""你觉得放什么好?"""
def __init__(
self,
midi_pitch: Optional[int],
midi_velocity: int,
start_time: int,
last_time: int,
mass_precision_time: int = 0,
extra_information: Dict[str, Any] = {},
):
"""
用于存储单个音符的类
Parameters
------------
midi_pitch: int
midi音高
midi_velocity: int
midi响度(力度)
start_time: int
开始之时(命令刻)
注:此处的时间是用从乐曲开始到当前的刻数
last_time: int
音符延续时间(命令刻)
mass_precision_time: int
高精度的开始时间偏移量(1/1250秒)
is_percussion: bool
是否作为打击乐器
distance: float
发声源距离玩家的距离(半径 `r`
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
azimuth: tuple[float, float]
声源方位
此参数为tuple包含两个元素分别表示
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
extra_information: Dict[str, Any]
附加信息,尽量存储为字典
Returns
---------
MineNote 类
"""
self.note_pitch: int = 66 if midi_pitch is None else midi_pitch
"""midi音高"""
self.velocity: int = midi_velocity
"""响度(力度)"""
self.start_tick: int = start_time
"""开始之时 命令刻"""
self.duration: int = last_time
"""音符持续时间 命令刻"""
self.high_precision_time: int = mass_precision_time
"""高精度开始时间偏量 0.4 毫秒"""
self.extra_info = extra_information if extra_information else {}
@classmethod
def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
"""自字节码析出 SingleNote 类"""
duration_ = (
group_1 := int.from_bytes(code_buffer[:6], "big")
) & 0b11111111111111111
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
note_velocity_ = (group_1 := group_1 >> 17) & 0b1111111
note_pitch_ = (group_1 := group_1 >> 7) & 0b1111111
try:
return cls(
midi_pitch=note_pitch_,
midi_velocity=note_velocity_,
start_time=start_tick_,
last_time=duration_,
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
)
except:
print(
"[Error] 单音符解析错误,字节码`{}`{}启用高精度时间偏移\n".format(
code_buffer, "" if is_high_time_precision else ""
)
)
raise
def encode(self, is_high_time_precision: bool = True) -> bytes:
"""
将数据打包为字节码
Parameters
------------
is_high_time_precision: bool
是否启用高精度,默认为**是**
Returns
---------
bytes
打包好的字节码
"""
# SingleNote 的字节码
# note_pitch 7 位 支持到 127
# velocity 长度 7 位 支持到 127
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# 共 48 位 合 6 字节
# high_time_precision可选长度 8 位 支持到 255 合 1 字节 支持 1/1250 秒]
return (
(
(
((((self.note_pitch << 7) + self.velocity) << 17) + self.start_tick)
<< 17
)
+ self.duration
).to_bytes(6, "big")
# + self.track_no.to_bytes(1, "big")
+ (
self.high_precision_time.to_bytes(1, "big")
if is_high_time_precision
else b""
)
)
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 TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
def get_info(self, key: str, default: Any = None) -> Any:
"""获取附加信息"""
return self.extra_info.get(key, default)
def stringize(self, include_extra_data: bool = False) -> str:
return "TrackedNote(Pitch = {}, Velocity = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
self.note_pitch,
self.velocity,
self.start_tick,
self.duration,
self.high_precision_time,
) + (
", ExtraData = {})".format(self.extra_info) if include_extra_data else ")"
)
# def __list__(self) -> List[int]:
# 我不认为这个类应当被作为列表使用
def __tuple__(
self,
) -> Tuple[int, int, int, int, int]:
return (
self.note_pitch,
self.velocity,
self.start_tick,
self.duration,
self.high_precision_time,
)
def __dict__(self):
return {
"Pitch": self.note_pitch,
"Velocity": self.velocity,
"StartTick": self.start_tick,
"Duration": self.duration,
"TimeOffset": self.high_precision_time,
"ExtraData": self.extra_info,
}
def __eq__(self, other) -> bool:
"""比较两个音符是否具有相同的属性,不计附加信息"""
if not isinstance(other, self.__class__):
return False
return self.__tuple__() == other.__tuple__()
def __lt__(self, other) -> bool:
"""比较自己是否在开始时间上早于另一个音符"""
if self.start_tick == other.start_tick:
return self.high_precision_time < other.high_precision_time
else:
return self.start_tick < other.start_tick
def __gt__(self, other) -> bool:
"""比较自己是否在开始时间上晚于另一个音符"""
if self.start_tick == other.start_tick:
return self.high_precision_time > other.high_precision_time
else:
return self.start_tick > other.start_tick
class SingleTrack(list):
"""存储单个轨道的类"""
track_name: str
"""轨道之名称"""
track_instrument: str
"""乐器ID"""
track_volume: float
"""该音轨的音量"""
is_high_time_precision: bool
"""该音轨是否使用高精度时间"""
is_percussive: bool
"""该音轨是否标记为打击乐器轨道"""
sound_position: SoundAtmos
"""声像方位"""
extra_info: Dict[str, Any]
"""你觉得放什么好?"""
def __init__(
self,
name: str = "未命名音轨",
instrument: str = "",
volume: float = 0,
precise_time: bool = True,
percussion: bool = False,
sound_direction: SoundAtmos = SoundAtmos(),
extra_information: Dict[str, Any] = {},
*args: SingleNote,
):
self.track_name = name
"""音轨名称"""
self.track_instrument = instrument
"""乐器ID"""
self.track_volume = volume
"""音量"""
self.is_high_time_precision = precise_time
"""是否使用高精度时间"""
self.is_percussive = percussion
"""是否为打击乐器"""
self.sound_position = sound_direction
"""声像方位"""
self.extra_info = extra_information if extra_information else {}
super().__init__(*args)
@property
def note_amount(self) -> int:
"""音符数"""
return len(self)
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 TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
def get_info(self, key: str, default: Any = None) -> Any:
"""获取附加信息"""
return self.extra_info.get(key, default)