给部分数据存储内容增设copy和delete函数

This commit is contained in:
Eilles
2026-07-03 03:04:40 +08:00
parent 8cd6865bf4
commit 6940a211b4
3 changed files with 225 additions and 32 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
# 汉钰律许可协议,第一版
**总第一版 次修订 · 二〇二四年七月七日编 二〇二五年四月二十六日修订**
**总第一版 次修订 · 二〇二四年七月七日编 二〇二六年七月三日修订**
## 一、重要须知
@@ -60,7 +60,7 @@
11. 如果用户对任何民事主体因其在进行本协议所授权进行的活动中侵犯该用户的专利而提起诉讼那么根据本协议授予该用户的所有关于此作品的任何其他专利许可将在提起上述诉讼之日起终止
12. 如果本作品作为用户的其他作品的不可分割的一部分进行任何民事活动本协议依旧对本作品即该用户的其他作品的一部分生效若本作品完全融入该用户的其他作品之中而不可独立存在则该用户需要保证其作品存在与本协议冲突的条款除非该作品已获得此作品颁发者的特殊许可或该用户即为此作品颁发者本人
12. 如果本作品作为用户的其他作品的不可分割的一部分进行任何民事活动本协议依旧对本作品即该用户的其他作品的一部分生效若本作品完全融入该用户的其他作品之中而不可独立存在则该用户需要保证其作品存在与本协议冲突的条款除非该作品已获得此作品颁发者的特殊许可或该用户即为此作品颁发者本人
## 四、使用条件
@@ -90,7 +90,7 @@
## 六、商标相关
本协议并未授予用户将颁发者的商标专属标记或特定产品名称用于合理或惯例性的描述或此类声明之外其他任何位置的权利
本协议不允许用户合理或惯例性的描述或者用于此类声明之外在任何其他位置使用颁发者的商标专用标记或特定产品名称
## 七、免责声明
+88 -2
View File
@@ -131,6 +131,9 @@ class SoundAtmos:
self.sound_distance, *self.sound_azimuth
)
def copy(self) -> "SoundAtmos":
return SoundAtmos(self.sound_distance, self.sound_azimuth)
@dataclass(init=False)
class SingleNote:
@@ -182,7 +185,7 @@ class SingleNote:
is_percussion: bool
是否作为打击乐器
extra_information: Dict[str, Any]
附加信息,尽量存储为字典
附加信息,尽量存储为字典,该内容不被任何“复制”方法保存
Returns
---------
@@ -202,6 +205,8 @@ class SingleNote:
self.extra_info = extra_information if extra_information else {}
@classmethod
def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
"""自字节码析出 SingleNote 类"""
@@ -308,6 +313,15 @@ class SingleNote:
", ExtraData = {})".format(self.extra_info) if include_extra_data else ")"
)
def copy(self) -> "SingleNote":
return SingleNote(
note_pitch=self.midi_pitch,
note_volume=self.volume,
start_tick=self.start_time,
keep_tick=self.duration,
mass_precision_time=self.high_precision_start_time,
)
# def __list__(self) -> List[int]:
# 我不认为这个类应当被作为列表使用
@@ -476,6 +490,30 @@ class SingleTrack(List[SingleNote]):
super().__init__(*args)
super().sort()
@classmethod
def from_note_list(
cls,
note_list: Iterable[SingleNote],
track_name: str = "未命名音轨",
track_instrument: str = "",
precise_time: bool = True,
percussion: bool = False,
sound_direction: Optional[SoundAtmos] = None,
extra_information: Dict[str, Any] = {},
) -> "SingleTrack":
"""从音符列表创建 SingleTrack 对象"""
single_track = cls(
track_name=track_name,
track_instrument=track_instrument,
precise_time=precise_time,
percussion=percussion,
sound_direction=sound_direction,
extra_information=extra_information,
)
single_track.update(note_list)
return single_track
def disable(self) -> None:
"""禁用音轨"""
@@ -519,8 +557,56 @@ class SingleTrack(List[SingleNote]):
super().extend(items)
super().sort() # =========================== TODO 需要优化
def copy(
self,
start_time: float = 0,
end_time: float = inf,
with_argument_curve: bool = False,
keep_zone_boundary_argument_value: bool = False,
global_boundary_argument_safe: bool = True,
) -> "SingleTrack":
"""
通过时间范围来复制音轨,返回一个音轨
"""
single_track = SingleTrack.from_note_list(
[x for x in self if start_time <= x.start_time <= end_time],
track_instrument=self.instrument,
precise_time=self.is_high_time_precision,
percussion=self.is_percussive,
sound_direction=self.sound_position.copy(),
)
if with_argument_curve:
single_track.argument_curves = {
item: (
None
if curve is None
else curve.copy(
start=start_time,
end=end_time,
keep_zone_boundary_value=keep_zone_boundary_argument_value,
global_boundary_safe=global_boundary_argument_safe,
)
)
for item, curve in self.argument_curves.items()
}
return single_track
def delete(
self, start_time: float, end_time: float, with_argument_curve: bool = False
):
"""
删除时间范围内的音符
"""
if with_argument_curve:
for curve in self.argument_curves.values():
if curve is not None:
curve.delete(start_time, end_time)
for i, note in enumerate(self):
if start_time <= note.start_time <= end_time:
del self[i]
def get(self, time: int) -> Generator[SingleNote, None, None]:
"""通过开始时间来获取音符"""
"""通过确切的开始时间来获取音符"""
return (x for x in self if x.start_time == time)
+134 -27
View File
@@ -24,7 +24,7 @@ Terms & Conditions: License.md in the root directory
# 可以等伶伦工作站开发出来后再进行完整的测试
from math import ceil
from math import ceil, inf
from dataclasses import dataclass
from typing import Optional, Any, List, Tuple, Callable
from enum import Enum
@@ -187,6 +187,16 @@ class Keyframe:
)
use_bezier: bool = False
def copy(self) -> "Keyframe":
return Keyframe(
time=self.time,
value=self.value,
out_interp=self.out_interp,
in_tangent=self.in_tangent,
out_tangent=self.out_tangent,
use_bezier=self.use_bezier,
)
class BoundaryBehaviour(str, Enum):
"""
@@ -225,7 +235,9 @@ class ParamCurve:
def __init__(
self,
base_value: float = 0.0,
default_interpolation_function: Callable[[float], float] = InterpolationMethod.linear,
default_interpolation_function: Callable[
[float], float
] = InterpolationMethod.linear,
boundary_mode: BoundaryBehaviour = BoundaryBehaviour.CONSTANT,
):
"""
@@ -251,6 +263,100 @@ class ParamCurve:
def __bool__(self) -> bool:
return bool(self._keys) or (self.base_line != 0)
def find_key(self, time: float) -> Tuple[int, Optional[Keyframe]]:
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
return idx, self._keys[idx]
else:
print("[警告] ParamCurve.find_key: 找不到指定时间点所对应之关键帧")
return idx, None
def copy(
self,
start: float = 0,
end: float = inf,
keep_zone_boundary_value: bool = False,
global_boundary_safe: bool = True,
) -> "ParamCurve":
"""
返回参数曲线在某时间段内的副本
Parameters
----------
start : float
起始时间。
end : float
结束时间。
keep_zone_boundary_value : bool
是否保留边界值(即在副本中的当前选区之边界创建以当前曲线值作为值的关键帧)。
global_boundary_safe : bool
当保留边界值启用时,是否忽略本身就处于参数曲线边界外的值,使其不创建关键帧。
Returns
-------
ParamCurve
参数曲线之副本
"""
param_curve = ParamCurve(
self.base_line,
self.base_interpolation_function,
self.boundary_behaviour,
)
if start >= end:
return param_curve
start_index, starter_keyframe = self.find_key(start)
end_index, ender_keyframe = self.find_key(end)
if starter_keyframe and ender_keyframe:
param_curve._keys = [
key.copy() for key in self._keys[start_index : end_index + 1]
]
elif starter_keyframe:
if end_index >= len(self._keys) and global_boundary_safe:
param_curve._keys = [key.copy() for key in self._keys[start_index:]]
else:
param_curve._keys = [
key.copy() for key in self._keys[start_index:end_index]
]
if keep_zone_boundary_value:
param_curve.add_key(end, self.value_at(end))
elif ender_keyframe:
if start_index <= 0 and global_boundary_safe:
param_curve._keys = [key.copy() for key in self._keys[:end_index]]
else:
param_curve._keys = [
key.copy() for key in self._keys[start_index : end_index + 1]
]
if keep_zone_boundary_value:
param_curve.add_key(start, self.value_at(start))
else:
if (
start_index <= 0
and end_index >= len(self._keys)
and global_boundary_safe
):
param_curve._keys = [key.copy() for key in self._keys]
else:
param_curve._keys = [
key.copy() for key in self._keys[start_index:end_index]
]
if keep_zone_boundary_value:
param_curve.add_key(start, self.value_at(start))
param_curve.add_key(end, self.value_at(end))
return param_curve
def delete(self, start: float, end: float):
"""
删除参数曲线在某时间段内的关键帧
"""
if start > end:
return
start_index, starter_keyframe = self.find_key(start)
end_index, ender_keyframe = self.find_key(end)
if ender_keyframe:
del self._keys[start_index : end_index + 1]
else:
del self._keys[start_index:end_index]
def add_key(
self,
time: float,
@@ -291,8 +397,8 @@ class ParamCurve:
)
new_key = Keyframe(time, value, interp, in_tangent, out_tangent, use_bezier)
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
idx, old_key = self.find_key(time)
if old_key:
self._keys[idx] = new_key
else:
self._keys.insert(idx, new_key)
@@ -310,17 +416,21 @@ class ParamCurve:
-------
None
"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
idx, key = self.find_key(time)
if key:
del self._keys[idx]
def update_key_value(self, time: float, new_value: float):
"""更新关键帧值,保留其他属性。"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
k = self._keys[idx]
idx, key = self.find_key(time)
if key:
self._keys[idx] = Keyframe(
time, new_value, k.out_interp, k.in_tangent, k.out_tangent, k.use_bezier
time,
new_value,
key.out_interp,
key.in_tangent,
key.out_tangent,
key.use_bezier,
)
def update_key_interp(
@@ -332,11 +442,10 @@ class ParamCurve:
use_bezier: bool = False,
):
"""更新关键帧的插值属性。"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
k = self._keys[idx]
new_value = k.value
interp = out_interp if out_interp is not None else k.out_interp
idx, key = self.find_key(time)
if key:
new_value = key.value
interp = out_interp if out_interp is not None else key.out_interp
self._keys[idx] = Keyframe(
time, new_value, interp, in_tangent, out_tangent, use_bezier
)
@@ -349,13 +458,12 @@ class ParamCurve:
use_bezier: bool = True,
):
"""单独设置关键帧的切线,不改变值。"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
k = self._keys[idx]
idx, key = self.find_key(time)
if key:
self._keys[idx] = Keyframe(
time,
k.value,
out_interp=k.out_interp,
key.value,
out_interp=key.out_interp,
in_tangent=in_tangent,
out_tangent=out_tangent,
use_bezier=use_bezier,
@@ -366,9 +474,8 @@ class ParamCurve:
将关键帧设为“平滑”模式(自动对称切线,并设为贝塞尔模式)。
切线长度基于相邻关键帧的时间和值差。
"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
k = self._keys[idx]
idx, key = self.find_key(time)
if key:
prev_k = self._keys[idx - 1] if idx > 0 else None
next_k = self._keys[idx + 1] if idx + 1 < len(self._keys) else None
@@ -382,13 +489,13 @@ class ParamCurve:
dt_in = dt_out = dt_total / 3.0
dv_in = dv_out = dv_total / 3.0
elif prev_k:
dt_out = (k.time - prev_k.time) / 2.0
dv_out = (k.value - prev_k.value) / 2.0
dt_out = (key.time - prev_k.time) / 2.0
dv_out = (key.value - prev_k.value) / 2.0
dt_in = dt_out
dv_in = dv_out
elif next_k:
dt_in = (next_k.time - k.time) / 2.0
dv_in = (next_k.value - k.value) / 2.0
dt_in = (next_k.time - key.time) / 2.0
dv_in = (next_k.value - key.value) / 2.0
dt_out = dt_in
dv_out = dv_in