mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-08-02 08:24:53 +00:00
小重构:音量vs距离新的计算方案
This commit is contained in:
@@ -33,6 +33,10 @@ vanilla_assets/
|
|||||||
/*.mid
|
/*.mid
|
||||||
/*.midi
|
/*.midi
|
||||||
|
|
||||||
|
# Fack Apple
|
||||||
|
.DS_Store
|
||||||
|
# Fack Microsoft
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
# Byte-compiled / optimized
|
# Byte-compiled / optimized
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__version__ = "3.0.0-alpha"
|
__version__ = "3.0.0-beta"
|
||||||
|
|
||||||
__author__ = (
|
__author__ = (
|
||||||
("金羿", "Eilles"),
|
("金羿", "Eilles"),
|
||||||
@@ -44,6 +44,8 @@ from .data import (
|
|||||||
SoundAtmos,
|
SoundAtmos,
|
||||||
MineNote,
|
MineNote,
|
||||||
CurvableParam,
|
CurvableParam,
|
||||||
|
Instrument,
|
||||||
|
DefaultInstrument,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .plugins import load_plugin_from_module
|
from .plugins import load_plugin_from_module
|
||||||
@@ -64,7 +66,10 @@ __all__ = [
|
|||||||
"SoundAtmos",
|
"SoundAtmos",
|
||||||
"MineNote",
|
"MineNote",
|
||||||
"CurvableParam",
|
"CurvableParam",
|
||||||
|
"Instrument",
|
||||||
# 工程项目相关
|
# 工程项目相关
|
||||||
"load_plugin_from_module",
|
"load_plugin_from_module",
|
||||||
"MusiCreater",
|
"MusiCreater",
|
||||||
|
# 默认乐器
|
||||||
|
"DefaultInstrument",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -62,3 +62,47 @@ def incremental_save_path(
|
|||||||
).exists():
|
).exists():
|
||||||
now_appendix += 1
|
now_appendix += 1
|
||||||
return ss_path
|
return ss_path
|
||||||
|
|
||||||
|
|
||||||
|
def volume_weight_to_sound_distance(
|
||||||
|
max_weight: float, minimal_loadness: int, weight: float, loadness: int
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
通过音量权计算声源距离
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
==========
|
||||||
|
max_weight: float
|
||||||
|
全曲最大音量权
|
||||||
|
minimal_loadness: int
|
||||||
|
曲目中响度最小乐器的响度
|
||||||
|
weight: float
|
||||||
|
当前乐器的音量权
|
||||||
|
loadness: int
|
||||||
|
当前乐器的响度
|
||||||
|
|
||||||
|
Returns
|
||||||
|
=======
|
||||||
|
float
|
||||||
|
声源距离
|
||||||
|
|
||||||
|
Notes
|
||||||
|
=====
|
||||||
|
|
||||||
|
以下关于距离的计算逻辑请见[计算过程](./resources/响度公式/计算过程.jpg)\n
|
||||||
|
关于响度的计算方法请见[此脚本](./resources/experiments/exam_loadness.py)\n
|
||||||
|
最终公式为:\n
|
||||||
|
声源距离 = 48 - (48 * 音量权 * ( 10 ** ((当前乐器的响度 - 响度最小乐器的响度) / 20 ))) / 全曲最大音量权\n
|
||||||
|
D_t = 48 - \\frac{48 \\cdot P_t \\cdot 10^{\\frac{V_t - V_x}{20}}}{P_m}
|
||||||
|
由于我们存储的响度都是 百-LUFS,所以下面的计算除数为 2000
|
||||||
|
"""
|
||||||
|
|
||||||
|
if weight == 0:
|
||||||
|
return 48
|
||||||
|
elif weight == max_weight:
|
||||||
|
# print("音量权最大,差比:", ((loadness - minimal_loadness) / 2000), ",率:", (1 - pow(10, (minimal_loadness - loadness)/2000)))
|
||||||
|
return 48 * (1 - pow(10, (minimal_loadness - loadness) / 2000))
|
||||||
|
else:
|
||||||
|
return 48 * (
|
||||||
|
1 - pow(10, (minimal_loadness - loadness) / 2000) * weight / max_weight
|
||||||
|
)
|
||||||
|
|||||||
@@ -33,20 +33,13 @@ from .constants import (
|
|||||||
MM_NBS_PERCUSSION_INSTRUMENT_TABLE,
|
MM_NBS_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .utils import (
|
from .utils import panning_2_rotation_linear, panning_2_rotation_trigonometric
|
||||||
volume_2_distance_natural,
|
|
||||||
volume_2_distance_straight,
|
|
||||||
panning_2_rotation_linear,
|
|
||||||
panning_2_rotation_trigonometric,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# 插件参数和插件本体类
|
# 插件参数和插件本体类
|
||||||
"MidiImportConfig",
|
"MidiImportConfig",
|
||||||
"MidiImport2MusicPlugin",
|
"MidiImport2MusicPlugin",
|
||||||
# 内置的拟合函数
|
# 内置的拟合函数
|
||||||
"volume_2_distance_straight",
|
|
||||||
"volume_2_distance_natural",
|
|
||||||
"panning_2_rotation_linear",
|
"panning_2_rotation_linear",
|
||||||
"panning_2_rotation_trigonometric",
|
"panning_2_rotation_trigonometric",
|
||||||
# Midi 相关默认值
|
# Midi 相关默认值
|
||||||
|
|||||||
@@ -23,7 +23,15 @@ from enum import Enum
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping
|
from typing import BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping
|
||||||
|
|
||||||
from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos
|
from Musicreater import (
|
||||||
|
SingleMusic,
|
||||||
|
SingleTrack,
|
||||||
|
SingleNote,
|
||||||
|
SoundAtmos,
|
||||||
|
CurvableParam,
|
||||||
|
ParamCurve,
|
||||||
|
Instrument,
|
||||||
|
)
|
||||||
from Musicreater.plugins import (
|
from Musicreater.plugins import (
|
||||||
music_input_plugin,
|
music_input_plugin,
|
||||||
PluginConfig,
|
PluginConfig,
|
||||||
@@ -31,7 +39,9 @@ from Musicreater.plugins import (
|
|||||||
PluginTypes,
|
PluginTypes,
|
||||||
MusicInputPluginBase,
|
MusicInputPluginBase,
|
||||||
)
|
)
|
||||||
|
from Musicreater.constants import SoundInformation, MC_INSTRUMENT_SOUND_INFO_TABLE
|
||||||
from Musicreater.exceptions import ZeroSpeedError, IllegalMinimumVolumeError
|
from Musicreater.exceptions import ZeroSpeedError, IllegalMinimumVolumeError
|
||||||
|
from Musicreater.paramcurve import Keyframe as ParamKeyframe
|
||||||
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||||
|
|
||||||
from .constants import (
|
from .constants import (
|
||||||
@@ -46,7 +56,6 @@ from .exceptions import (
|
|||||||
LyricMismatchError,
|
LyricMismatchError,
|
||||||
)
|
)
|
||||||
from .utils import (
|
from .utils import (
|
||||||
volume_2_distance_natural,
|
|
||||||
panning_2_rotation_trigonometric,
|
panning_2_rotation_trigonometric,
|
||||||
midi_msgs_to_noteinfo,
|
midi_msgs_to_noteinfo,
|
||||||
)
|
)
|
||||||
@@ -76,9 +85,10 @@ class MidiImportConfig(PluginConfig):
|
|||||||
pitched_note_reference_table: Mapping[int, str] = None # type: ignore
|
pitched_note_reference_table: Mapping[int, str] = None # type: ignore
|
||||||
percussion_note_reference_table: Mapping[int, str] = None # type: ignore
|
percussion_note_reference_table: Mapping[int, str] = None # type: ignore
|
||||||
note_replacement_table: Mapping[str, str] = None # type: ignore
|
note_replacement_table: Mapping[str, str] = None # type: ignore
|
||||||
|
instrument_information_table: Mapping[str, SoundInformation] = None # type: ignore
|
||||||
|
|
||||||
# 参数转换函数
|
# 参数转换相关
|
||||||
volume_process_function: Callable[[float], float] = volume_2_distance_natural
|
keep_velocity_to_param_curve: bool = True
|
||||||
panning_processing_function: Callable[[float], float] = (
|
panning_processing_function: Callable[[float], float] = (
|
||||||
panning_2_rotation_trigonometric
|
panning_2_rotation_trigonometric
|
||||||
)
|
)
|
||||||
@@ -104,6 +114,11 @@ class MidiImportConfig(PluginConfig):
|
|||||||
self.note_replacement_table = (
|
self.note_replacement_table = (
|
||||||
self.note_replacement_table if self.note_replacement_table else {}
|
self.note_replacement_table if self.note_replacement_table else {}
|
||||||
)
|
)
|
||||||
|
self.instrument_information_table = (
|
||||||
|
self.instrument_information_table
|
||||||
|
if self.instrument_information_table
|
||||||
|
else MC_INSTRUMENT_SOUND_INFO_TABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ControlerKeys(Enum):
|
class ControlerKeys(Enum):
|
||||||
@@ -140,6 +155,8 @@ class TrackDivisionDict(
|
|||||||
division_by_volume: bool = False
|
division_by_volume: bool = False
|
||||||
division_by_panning: bool = False
|
division_by_panning: bool = False
|
||||||
|
|
||||||
|
create_volume_curve: bool = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*args,
|
*args,
|
||||||
@@ -173,7 +190,9 @@ class TrackDivisionDict(
|
|||||||
try:
|
try:
|
||||||
return super().__getitem__(key)
|
return super().__getitem__(key)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self[key] = SingleTrack()
|
self[key] = SingleTrack(precise_time=True)
|
||||||
|
if self.create_volume_curve:
|
||||||
|
self[key].argument_curves[CurvableParam.VOLUME] = ParamCurve()
|
||||||
return self[key]
|
return self[key]
|
||||||
|
|
||||||
|
|
||||||
@@ -401,50 +420,51 @@ class MidiImport2MusicPlugin(MusicInputPluginBase):
|
|||||||
|
|
||||||
# 更新结果信息
|
# 更新结果信息
|
||||||
|
|
||||||
that_note, sound_name, orign_distance, sound_rotation = (
|
that_note, sound_name, sound_rotation = midi_msgs_to_noteinfo(
|
||||||
midi_msgs_to_noteinfo(
|
inst=(
|
||||||
inst=(
|
msg.note
|
||||||
msg.note
|
if (_is_percussion := (msg.channel == 9))
|
||||||
if (_is_percussion := (msg.channel == 9))
|
else _program
|
||||||
else _program
|
),
|
||||||
),
|
note=(_program if _is_percussion else msg.note),
|
||||||
note=(_program if _is_percussion else msg.note),
|
percussive=_is_percussion,
|
||||||
percussive=_is_percussion,
|
volume=_volume,
|
||||||
volume=_volume,
|
velocity=_velocity,
|
||||||
velocity=_velocity,
|
panning=_panning,
|
||||||
panning=_panning,
|
start_time=_start_ms, # 微秒
|
||||||
start_time=_start_ms, # 微秒
|
duration=microseconds - _start_ms, # 微秒
|
||||||
duration=microseconds - _start_ms, # 微秒
|
play_speed=config.speed_multiplier,
|
||||||
play_speed=config.speed_multiplier,
|
midi_reference_table=(
|
||||||
midi_reference_table=(
|
config.percussion_note_reference_table
|
||||||
config.percussion_note_reference_table
|
if _is_percussion
|
||||||
if _is_percussion
|
else config.pitched_note_reference_table
|
||||||
else config.pitched_note_reference_table
|
),
|
||||||
),
|
panning_processing_method=config.panning_processing_function,
|
||||||
volume_processing_method=config.volume_process_function,
|
note_table_replacement=config.note_replacement_table,
|
||||||
panning_processing_method=config.panning_processing_function,
|
lyric_line=(
|
||||||
note_table_replacement=config.note_replacement_table,
|
_lyric.encode("latin1").decode(config.string_encoding)
|
||||||
lyric_line=(
|
if config.string_encoding
|
||||||
_lyric.encode("latin1").decode(
|
else _lyric
|
||||||
config.string_encoding
|
),
|
||||||
)
|
|
||||||
if config.string_encoding
|
|
||||||
else _lyric
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# print(that_note.start_time, end=", ")
|
# print(that_note.start_time, end=", ")
|
||||||
|
|
||||||
divided_tracks[
|
now_track = divided_tracks[
|
||||||
(
|
(
|
||||||
track_no,
|
track_no,
|
||||||
msg.channel,
|
msg.channel,
|
||||||
sound_name,
|
sound_name,
|
||||||
orign_distance,
|
_volume,
|
||||||
sound_rotation,
|
sound_rotation,
|
||||||
)
|
)
|
||||||
].add(that_note)
|
]
|
||||||
|
now_track.add(that_note)
|
||||||
|
if config.keep_velocity_to_param_curve:
|
||||||
|
now_track.argument_curves[CurvableParam.VOLUME].add_key( # type: ignore
|
||||||
|
time=that_note.precise_start_time,
|
||||||
|
value=_velocity / 127,
|
||||||
|
)
|
||||||
|
|
||||||
# 更新统计信息
|
# 更新统计信息
|
||||||
note_count += 1
|
note_count += 1
|
||||||
@@ -500,11 +520,15 @@ class MidiImport2MusicPlugin(MusicInputPluginBase):
|
|||||||
): # 音轨编号
|
): # 音轨编号
|
||||||
every_single_track.name = track_name
|
every_single_track.name = track_name
|
||||||
if track_properties[2]: # 乐器名称
|
if track_properties[2]: # 乐器名称
|
||||||
every_single_track.instrument = track_properties[2]
|
every_single_track.instrument = Instrument(
|
||||||
if track_properties[3]: # 音量
|
track_properties[2],
|
||||||
every_single_track.sound_position.sound_distance = track_properties[3]
|
config.instrument_information_table[track_properties[2]]["C-LUFS"],
|
||||||
|
)
|
||||||
|
if track_properties[3]: # 音量权
|
||||||
|
every_single_track.__volume_weight = track_properties[3]
|
||||||
if track_properties[4]: # 声相
|
if track_properties[4]: # 声相
|
||||||
every_single_track.sound_position.sound_azimuth = track_properties[4]
|
every_single_track.sound_position = track_properties[4]
|
||||||
final_music.append(every_single_track)
|
super(SingleMusic, final_music).append(every_single_track)
|
||||||
|
final_music._calc_values()
|
||||||
|
# print("所有声源距离表:",{i:t.sound_distance for i, t in enumerate(final_music)})
|
||||||
return final_music
|
return final_music
|
||||||
|
|||||||
@@ -24,50 +24,6 @@ from typing import Callable, Dict, List, Optional, Sequence, Tuple, Mapping
|
|||||||
from Musicreater import SingleNote, SoundAtmos
|
from Musicreater import SingleNote, SoundAtmos
|
||||||
|
|
||||||
|
|
||||||
def volume_2_distance_natural(
|
|
||||||
vol: float,
|
|
||||||
) -> float:
|
|
||||||
"""
|
|
||||||
Midi 力度值/音量值拟合成的距离函数,一种更加自然的听感?
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
vol: int
|
|
||||||
Midi 音符力度值(0~127)
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
float播放中心到玩家的距离
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
-8.081720684086314
|
|
||||||
* math.log(
|
|
||||||
vol + 14.579508825070013,
|
|
||||||
)
|
|
||||||
+ 37.65806375944386
|
|
||||||
if vol < 60.64
|
|
||||||
else 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649)
|
|
||||||
+ -6.313841334963396 * (vol + 2592.272889454798)
|
|
||||||
+ 4558.496367823575
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def volume_2_distance_straight(vol: float) -> float:
|
|
||||||
"""
|
|
||||||
Midi 力度值/音量值拟合成的距离函数,线性转换
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
vol: int
|
|
||||||
Midi 音符力度值(0~127)
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
float播放中心到玩家的距离
|
|
||||||
"""
|
|
||||||
return (vol + 1) / -8 + 16
|
|
||||||
|
|
||||||
|
|
||||||
def panning_2_rotation_linear(pan_: float) -> float:
|
def panning_2_rotation_linear(pan_: float) -> float:
|
||||||
"""
|
"""
|
||||||
Midi 左右平衡偏移值线性转为声源旋转角度
|
Midi 左右平衡偏移值线性转为声源旋转角度
|
||||||
@@ -147,11 +103,10 @@ def midi_msgs_to_noteinfo(
|
|||||||
duration: int,
|
duration: int,
|
||||||
play_speed: float,
|
play_speed: float,
|
||||||
midi_reference_table: Mapping[int, str],
|
midi_reference_table: Mapping[int, str],
|
||||||
volume_processing_method: Callable[[float], float],
|
|
||||||
panning_processing_method: Callable[[float], float],
|
panning_processing_method: Callable[[float], float],
|
||||||
note_table_replacement: Mapping[str, str] = {},
|
note_table_replacement: Mapping[str, str] = {},
|
||||||
lyric_line: str = "",
|
lyric_line: str = "",
|
||||||
) -> Tuple[SingleNote, str, float, Tuple[float, float]]:
|
) -> Tuple[SingleNote, str, Tuple[float, float]]:
|
||||||
"""
|
"""
|
||||||
将 Midi信息转为音符对象
|
将 Midi信息转为音符对象
|
||||||
|
|
||||||
@@ -177,8 +132,6 @@ def midi_msgs_to_noteinfo(
|
|||||||
曲目播放速度
|
曲目播放速度
|
||||||
midi_reference_table: Dict[int, str]
|
midi_reference_table: Dict[int, str]
|
||||||
转换对照表
|
转换对照表
|
||||||
volume_processing_method: Callable[[float], float]
|
|
||||||
音量处理函数
|
|
||||||
panning_processing_method: Callable[[float], float]
|
panning_processing_method: Callable[[float], float]
|
||||||
立体声相偏移处理函数
|
立体声相偏移处理函数
|
||||||
note_table_replacement: Dict[str, str]
|
note_table_replacement: Dict[str, str]
|
||||||
@@ -191,7 +144,6 @@ def midi_msgs_to_noteinfo(
|
|||||||
Tuple[
|
Tuple[
|
||||||
MineNote我的世界音符对象,
|
MineNote我的世界音符对象,
|
||||||
str我的世界声音名,
|
str我的世界声音名,
|
||||||
float播放中心到玩家的距离,
|
|
||||||
Tuple[float, float]声源旋转角度
|
Tuple[float, float]声源旋转角度
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
@@ -204,7 +156,6 @@ def midi_msgs_to_noteinfo(
|
|||||||
return (
|
return (
|
||||||
SingleNote(
|
SingleNote(
|
||||||
note_pitch=note,
|
note_pitch=note,
|
||||||
note_volume=velocity, # 需要重新设计
|
|
||||||
start_tick=(tk := int(start_time / float(play_speed) / 50000)),
|
start_tick=(tk := int(start_time / float(play_speed) / 50000)),
|
||||||
keep_tick=round(duration / float(play_speed) / 50000),
|
keep_tick=round(duration / float(play_speed) / 50000),
|
||||||
mass_precision_time=round(
|
mass_precision_time=round(
|
||||||
@@ -212,11 +163,11 @@ def midi_msgs_to_noteinfo(
|
|||||||
),
|
),
|
||||||
extra_information={
|
extra_information={
|
||||||
"LYRIC_TEXT": lyric_line,
|
"LYRIC_TEXT": lyric_line,
|
||||||
|
"VELOCITY_VALUE": velocity,
|
||||||
"VOLUME_VALUE": volume,
|
"VOLUME_VALUE": volume,
|
||||||
"PIN_VALUE": panning,
|
"PIN_VALUE": panning,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
note_table_replacement.get(mc_sound_ID, mc_sound_ID),
|
note_table_replacement.get(mc_sound_ID, mc_sound_ID),
|
||||||
volume_processing_method(volume),
|
|
||||||
(panning_processing_method(panning), 0),
|
(panning_processing_method(panning), 0),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -510,7 +510,6 @@ class NoteDataConvert2CommandPlugin(LibraryPluginBase):
|
|||||||
|
|
||||||
(
|
(
|
||||||
relative_coordinates,
|
relative_coordinates,
|
||||||
volume_percentage,
|
|
||||||
mc_pitch,
|
mc_pitch,
|
||||||
) = minenote_to_command_parameters(
|
) = minenote_to_command_parameters(
|
||||||
note,
|
note,
|
||||||
@@ -527,10 +526,9 @@ class NoteDataConvert2CommandPlugin(LibraryPluginBase):
|
|||||||
.replace("(", r"{")
|
.replace("(", r"{")
|
||||||
.replace(")", r"}")
|
.replace(")", r"}")
|
||||||
)
|
)
|
||||||
+ r"playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
+ "playsound {} @s ^{} ^{} ^{} 3.0 {} {}".format(
|
||||||
track.instrument,
|
track.instrument,
|
||||||
*relative_coordinates,
|
*relative_coordinates,
|
||||||
volume_percentage,
|
|
||||||
1.0 if note.percussive else mc_pitch,
|
1.0 if note.percussive else mc_pitch,
|
||||||
minimum_volume,
|
minimum_volume,
|
||||||
)
|
)
|
||||||
@@ -595,7 +593,6 @@ class NoteDataConvert2CommandPlugin(LibraryPluginBase):
|
|||||||
|
|
||||||
(
|
(
|
||||||
relative_coordinates,
|
relative_coordinates,
|
||||||
volume_percentage,
|
|
||||||
mc_pitch,
|
mc_pitch,
|
||||||
) = minenote_to_command_parameters(
|
) = minenote_to_command_parameters(
|
||||||
note,
|
note,
|
||||||
@@ -606,10 +603,9 @@ class NoteDataConvert2CommandPlugin(LibraryPluginBase):
|
|||||||
MineCommand(
|
MineCommand(
|
||||||
command=(
|
command=(
|
||||||
execute_command_head.format(player_selector)
|
execute_command_head.format(player_selector)
|
||||||
+ "playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
+ "playsound {} @s ^{} ^{} ^{} 3.0 {} {}".format(
|
||||||
note.instrument,
|
note.instrument,
|
||||||
*relative_coordinates,
|
*relative_coordinates,
|
||||||
volume_percentage,
|
|
||||||
1.0 if note.percussive else mc_pitch,
|
1.0 if note.percussive else mc_pitch,
|
||||||
minimum_volume,
|
minimum_volume,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ def minenote_to_command_parameters(
|
|||||||
pitch_deviation: float = 0,
|
pitch_deviation: float = 0,
|
||||||
) -> Tuple[
|
) -> Tuple[
|
||||||
Tuple[float, float, float],
|
Tuple[float, float, float],
|
||||||
float,
|
|
||||||
Union[float, Literal[None]],
|
Union[float, Literal[None]],
|
||||||
]:
|
]:
|
||||||
"""
|
"""
|
||||||
@@ -53,13 +52,12 @@ def minenote_to_command_parameters(
|
|||||||
|
|
||||||
返回
|
返回
|
||||||
----
|
----
|
||||||
tuple[float, float, float], float, float
|
tuple[float, float, float], float
|
||||||
播放视角坐标, 指令音量参数, 指令音调参数
|
播放视角坐标, 指令音调参数
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
mine_note.position.position_displacement,
|
mine_note.position.position_displacement,
|
||||||
mine_note.volume / 127,
|
|
||||||
(
|
(
|
||||||
None
|
None
|
||||||
if mine_note.percussive
|
if mine_note.percussive
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
# from .types import Dict, List, Tuple, MidiInstrumentTableType, MidiNoteNameTableType
|
# from .types import Dict, List, Tuple, MidiInstrumentTableType, MidiNoteNameTableType
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple, TypedDict
|
||||||
|
|
||||||
x = "x"
|
x = "x"
|
||||||
"""
|
"""
|
||||||
@@ -438,7 +438,17 @@ MC_INSTRUMENT_BLOCKS_TABLE: Dict[str, Tuple[str, ...]] = {
|
|||||||
}
|
}
|
||||||
"""MC乐器对音符盒下垫方块对照表"""
|
"""MC乐器对音符盒下垫方块对照表"""
|
||||||
|
|
||||||
MC_INSTRUMENT_SOUND_INFO_TABLE: Dict[str, Dict[str, float]] = {
|
SoundInformation = TypedDict(
|
||||||
|
"SoundInformation",
|
||||||
|
{
|
||||||
|
"C-LUFS": int,
|
||||||
|
"MS": float,
|
||||||
|
"SR": int,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""MC乐器信息类定义"""
|
||||||
|
|
||||||
|
MC_INSTRUMENT_SOUND_INFO_TABLE: Dict[str, SoundInformation] = {
|
||||||
"note.bass": {"C-LUFS": -1178, "MS": 532.0, "SR": 7218},
|
"note.bass": {"C-LUFS": -1178, "MS": 532.0, "SR": 7218},
|
||||||
"note.bassattack": {"C-LUFS": -1188, "MS": 484.8, "SR": 7921},
|
"note.bassattack": {"C-LUFS": -1188, "MS": 484.8, "SR": 7921},
|
||||||
"note.snare": {"C-LUFS": -1173, "MS": 80.0, "SR": 48000},
|
"note.snare": {"C-LUFS": -1173, "MS": 80.0, "SR": 48000},
|
||||||
@@ -514,6 +524,7 @@ MC_INSTRUMENT_VOLUME_BALANCE_TABLE: Dict[str, float] = {
|
|||||||
"mob.zombie.wood": 0.32247793193163765,
|
"mob.zombie.wood": 0.32247793193163765,
|
||||||
}
|
}
|
||||||
"""乐器响度平衡表,倍率"""
|
"""乐器响度平衡表,倍率"""
|
||||||
|
# 上表用处不大
|
||||||
|
|
||||||
MC_EILLES_RT261_INSTRUMENT_REPLACE_TABLE: Dict[str, str] = {
|
MC_EILLES_RT261_INSTRUMENT_REPLACE_TABLE: Dict[str, str] = {
|
||||||
"note.trumpet": "note.flute",
|
"note.trumpet": "note.flute",
|
||||||
@@ -625,8 +636,4 @@ MM_INSTRUMENT_DEVIATION_TABLE: Dict[str, int] = {
|
|||||||
|
|
||||||
# 金羿ELS 音符方块对照表
|
# 金羿ELS 音符方块对照表
|
||||||
|
|
||||||
MN_EILLES_NOTE_BLOCK_TABLE: Dict[int, str] = {
|
MN_EILLES_NOTE_BLOCK_TABLE: Dict[int, str] = {}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+472
-94
@@ -29,6 +29,7 @@ from typing import (
|
|||||||
Optional,
|
Optional,
|
||||||
Any,
|
Any,
|
||||||
List,
|
List,
|
||||||
|
SupportsIndex,
|
||||||
Tuple,
|
Tuple,
|
||||||
Union,
|
Union,
|
||||||
Dict,
|
Dict,
|
||||||
@@ -42,10 +43,17 @@ from typing import (
|
|||||||
Hashable,
|
Hashable,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Mapping,
|
Mapping,
|
||||||
|
overload,
|
||||||
)
|
)
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from .exceptions import SingleNoteDecodeError, ParameterTypeError, ParameterValueError
|
from ._utils import volume_weight_to_sound_distance
|
||||||
|
from .exceptions import (
|
||||||
|
SingleNoteDecodeError,
|
||||||
|
ParameterTypeError,
|
||||||
|
ParameterValueError,
|
||||||
|
ParameterCacheUnfreshError,
|
||||||
|
)
|
||||||
from .paramcurve import ParamCurve
|
from .paramcurve import ParamCurve
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
@@ -149,6 +157,44 @@ class SoundAtmos:
|
|||||||
return SoundAtmos(self.sound_distance, self.sound_azimuth)
|
return SoundAtmos(self.sound_distance, self.sound_azimuth)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Instrument:
|
||||||
|
"""乐器类"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
"""乐器名称"""
|
||||||
|
|
||||||
|
loadness: int
|
||||||
|
"""乐器响度 单位`百 LUFS`"""
|
||||||
|
|
||||||
|
def copy(self) -> "Instrument":
|
||||||
|
return Instrument(self.name, self.loadness)
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultInstrument(Enum):
|
||||||
|
BASS = Instrument("note.bass", -1178)
|
||||||
|
BASSATTACK = Instrument("note.bassattack", -1188)
|
||||||
|
SNARE = Instrument("note.snare", -1173)
|
||||||
|
HAT = Instrument("note.hat", -1480)
|
||||||
|
BD = Instrument("note.bd", -957)
|
||||||
|
BELL = Instrument("note.bell", -2510)
|
||||||
|
FLUTE = Instrument("note.flute", -2076)
|
||||||
|
CHIME = Instrument("note.chime", -2984)
|
||||||
|
GUITAR = Instrument("note.guitar", -2539)
|
||||||
|
XYLOPHONE = Instrument("note.xylophone", -2419)
|
||||||
|
IRON_XYLOPHONE = Instrument("note.iron_xylophone", -2078)
|
||||||
|
COW_BELL = Instrument("note.cow_bell", -1427)
|
||||||
|
DIDGERIDOO = Instrument("note.didgeridoo", -2062)
|
||||||
|
BIT = Instrument("note.bit", -2677)
|
||||||
|
BANJO = Instrument("note.banjo", -2207)
|
||||||
|
PLING = Instrument("note.pling", -1528)
|
||||||
|
TRUMPET = Instrument("note.trumpet", -1678)
|
||||||
|
TRUMPET_EXPOSED = Instrument("note.trumpet_exposed", -1682)
|
||||||
|
TRUMPET_OXIDIZED = Instrument("note.trumpet_oxidized", -2225)
|
||||||
|
TRUMPET_WEATHERED = Instrument("note.trumpet_weathered", -1875)
|
||||||
|
HARP = Instrument("note.harp", -1448)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class SingleNote:
|
class SingleNote:
|
||||||
"""存储单个音符的类"""
|
"""存储单个音符的类"""
|
||||||
@@ -156,9 +202,6 @@ class SingleNote:
|
|||||||
midi_pitch: int
|
midi_pitch: int
|
||||||
"""Midi 音高"""
|
"""Midi 音高"""
|
||||||
|
|
||||||
volume: int
|
|
||||||
"""力度/播放响度 0~127 百廿七分比"""
|
|
||||||
|
|
||||||
start_time: int
|
start_time: int
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
|
|
||||||
@@ -174,7 +217,6 @@ class SingleNote:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
note_pitch: Optional[int],
|
note_pitch: Optional[int],
|
||||||
note_volume: int,
|
|
||||||
start_tick: int,
|
start_tick: int,
|
||||||
keep_tick: int,
|
keep_tick: int,
|
||||||
mass_precision_time: int = 0,
|
mass_precision_time: int = 0,
|
||||||
@@ -187,8 +229,6 @@ class SingleNote:
|
|||||||
------------
|
------------
|
||||||
midi_pitch: int
|
midi_pitch: int
|
||||||
Midi 音高
|
Midi 音高
|
||||||
note_volume: int
|
|
||||||
响度或曰力度(百廿七分比, 0~127)
|
|
||||||
start_time: int
|
start_time: int
|
||||||
开始之时(命令刻)
|
开始之时(命令刻)
|
||||||
注:此处的时间是用从乐曲开始到当前的刻数
|
注:此处的时间是用从乐曲开始到当前的刻数
|
||||||
@@ -208,8 +248,6 @@ class SingleNote:
|
|||||||
|
|
||||||
self.midi_pitch: int = 66 if note_pitch is None else note_pitch
|
self.midi_pitch: int = 66 if note_pitch is None else note_pitch
|
||||||
"""Midi 音高"""
|
"""Midi 音高"""
|
||||||
self.volume: int = note_volume
|
|
||||||
"""响度(力度)"""
|
|
||||||
self.start_time: int = start_tick
|
self.start_time: int = start_tick
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
self.duration: int = keep_tick
|
self.duration: int = keep_tick
|
||||||
@@ -228,19 +266,17 @@ class SingleNote:
|
|||||||
def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
|
def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
|
||||||
"""自字节码析出 SingleNote 类"""
|
"""自字节码析出 SingleNote 类"""
|
||||||
duration_ = (
|
duration_ = (
|
||||||
group_1 := int.from_bytes(code_buffer[:6], "big")
|
group_1 := int.from_bytes(code_buffer[:5], "big")
|
||||||
) & 0b11111111111111111
|
) & 0b11111111111111111
|
||||||
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
|
start_tick_ = (group_1 := group_1 >> 17) & 0b111111111111111111
|
||||||
note_volume_ = (group_1 := group_1 >> 17) & 0b1111111
|
note_pitch_ = (group_1 := group_1 >> 18) & 0b1111111
|
||||||
note_pitch_ = (group_1 := group_1 >> 7) & 0b1111111
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return cls(
|
return cls(
|
||||||
note_pitch=note_pitch_,
|
note_pitch=note_pitch_,
|
||||||
note_volume=note_volume_,
|
|
||||||
start_tick=start_tick_,
|
start_tick=start_tick_,
|
||||||
keep_tick=duration_,
|
keep_tick=duration_,
|
||||||
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
|
mass_precision_time=code_buffer[5] if is_high_time_precision else 0,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 我也不知道为什么这里要放一个异常处理
|
# 我也不知道为什么这里要放一个异常处理
|
||||||
@@ -275,20 +311,15 @@ class SingleNote:
|
|||||||
# SingleNote 的字节码
|
# SingleNote 的字节码
|
||||||
|
|
||||||
# note_pitch 7 位 支持到 127
|
# note_pitch 7 位 支持到 127
|
||||||
# volume 长度 7 位 支持到 127
|
# start_tick 18 位 支持到 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 字节
|
# 共 40 位 合 5 字节
|
||||||
|
|
||||||
# high_time_precision(可选)长度 8 位 支持到 255 合 1 字节 单位为 1/5000 秒]
|
# high_time_precision(可选)长度 8 位 支持到 255 合 1 字节 单位为 1/5000 秒]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(
|
(
|
||||||
(
|
((((self.midi_pitch) << 18) + self.start_time) << 17) + self.duration
|
||||||
((((self.midi_pitch << 7) + self.volume) << 17) + self.start_time)
|
|
||||||
<< 17
|
|
||||||
)
|
|
||||||
+ self.duration
|
|
||||||
).to_bytes(6, "big")
|
).to_bytes(6, "big")
|
||||||
# + self.track_no.to_bytes(1, "big")
|
# + self.track_no.to_bytes(1, "big")
|
||||||
+ (
|
+ (
|
||||||
@@ -320,37 +351,38 @@ 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 = {}, Volume = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
|
return "Note(Pitch = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
|
||||||
self.midi_pitch,
|
self.midi_pitch,
|
||||||
self.volume,
|
|
||||||
self.start_time,
|
self.start_time,
|
||||||
self.duration,
|
self.duration,
|
||||||
self.high_precision_start_time,
|
self.high_precision_start_time,
|
||||||
) + (
|
) + (", ExtraData = {})".format(self.extra_info) if include_extra_data else ")")
|
||||||
", ExtraData = {})".format(self.extra_info) if include_extra_data else ")"
|
|
||||||
)
|
|
||||||
|
|
||||||
def copy(self) -> "SingleNote":
|
def copy(
|
||||||
|
self,
|
||||||
|
note_pitch: Optional[int] = None,
|
||||||
|
start_tick: Optional[int] = None,
|
||||||
|
keep_tick: Optional[int] = None,
|
||||||
|
mass_precision_time: Optional[int] = None,
|
||||||
|
) -> "SingleNote":
|
||||||
return SingleNote(
|
return SingleNote(
|
||||||
note_pitch=self.midi_pitch,
|
note_pitch=note_pitch or self.midi_pitch,
|
||||||
note_volume=self.volume,
|
start_tick=start_tick or self.start_time,
|
||||||
start_tick=self.start_time,
|
keep_tick=keep_tick or self.duration,
|
||||||
keep_tick=self.duration,
|
mass_precision_time=mass_precision_time or self.high_precision_start_time,
|
||||||
mass_precision_time=self.high_precision_start_time,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# def __list__(self) -> List[int]:
|
# def __list__(self) -> List[int]:
|
||||||
# 我不认为这个类应当被作为列表使用
|
# 我不认为这个类应当被作为列表使用
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
return bool(self.volume)
|
return self.duration > 0
|
||||||
|
|
||||||
def __tuple__(
|
def __tuple__(
|
||||||
self,
|
self,
|
||||||
) -> Tuple[int, int, int, int, int]:
|
) -> Tuple[int, int, int, int]:
|
||||||
return (
|
return (
|
||||||
self.midi_pitch,
|
self.midi_pitch,
|
||||||
self.volume,
|
|
||||||
self.start_time,
|
self.start_time,
|
||||||
self.duration,
|
self.duration,
|
||||||
self.high_precision_start_time,
|
self.high_precision_start_time,
|
||||||
@@ -359,7 +391,6 @@ class SingleNote:
|
|||||||
def __dict__(self):
|
def __dict__(self):
|
||||||
return {
|
return {
|
||||||
"Pitch": self.midi_pitch,
|
"Pitch": self.midi_pitch,
|
||||||
"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,
|
||||||
@@ -395,8 +426,6 @@ class MineNote:
|
|||||||
"""Midi 音高"""
|
"""Midi 音高"""
|
||||||
instrument: str
|
instrument: str
|
||||||
"""乐器标识"""
|
"""乐器标识"""
|
||||||
volume: float
|
|
||||||
"""力度或曰播放音量、响度 0~127 百廿七分比"""
|
|
||||||
start_tick: int
|
start_tick: int
|
||||||
"""开始之时 命令刻"""
|
"""开始之时 命令刻"""
|
||||||
duration_tick: int
|
duration_tick: int
|
||||||
@@ -418,30 +447,57 @@ class MineNote:
|
|||||||
cls,
|
cls,
|
||||||
note: SingleNote,
|
note: SingleNote,
|
||||||
note_instrument: str,
|
note_instrument: str,
|
||||||
|
note_volume_weight: 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_volume: float = 0.0,
|
adjust_note_volume_weight: 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,
|
||||||
adjust_note_updown_panning_degree: float = 0.0,
|
adjust_note_updown_panning_degree: float = 0.0,
|
||||||
) -> "MineNote":
|
) -> "MineNote":
|
||||||
"""从SingleNote对象创建MineNote对象"""
|
"""从SingleNote对象创建MineNote对象"""
|
||||||
sound_position.sound_distance += adjust_note_sound_distance
|
|
||||||
sound_position.sound_azimuth = (
|
|
||||||
sound_position.sound_azimuth[0] + adjust_note_leftright_panning_degree,
|
|
||||||
sound_position.sound_azimuth[1] + adjust_note_updown_panning_degree,
|
|
||||||
)
|
|
||||||
return cls(
|
return cls(
|
||||||
pitch=note.midi_pitch + adjust_note_pitch,
|
pitch=note.midi_pitch + adjust_note_pitch,
|
||||||
instrument=note_instrument,
|
instrument=note_instrument,
|
||||||
volume=note.volume + adjust_note_volume,
|
|
||||||
start_tick=note.start_time,
|
start_tick=note.start_time,
|
||||||
duration_tick=note.duration,
|
duration_tick=note.duration,
|
||||||
start_time_offset=note.high_precision_start_time if is_persiced_time else 0,
|
start_time_offset=note.high_precision_start_time if is_persiced_time else 0,
|
||||||
percussive=is_percussive_note,
|
percussive=is_percussive_note,
|
||||||
position=sound_position,
|
position=(
|
||||||
|
SoundAtmos(
|
||||||
|
sound_position.sound_distance
|
||||||
|
+ adjust_note_sound_distance
|
||||||
|
+ (
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(sound_position.sound_distance - 48)
|
||||||
|
* (note_volume_weight + adjust_note_volume_weight)
|
||||||
|
/ note_volume_weight
|
||||||
|
)
|
||||||
|
+ 48
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if adjust_note_volume_weight
|
||||||
|
else 0
|
||||||
|
),
|
||||||
|
(
|
||||||
|
sound_position.sound_azimuth[0]
|
||||||
|
+ adjust_note_leftright_panning_degree,
|
||||||
|
sound_position.sound_azimuth[1]
|
||||||
|
+ adjust_note_updown_panning_degree,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
adjust_note_volume_weight
|
||||||
|
or adjust_note_sound_distance
|
||||||
|
or adjust_note_leftright_panning_degree
|
||||||
|
or adjust_note_updown_panning_degree
|
||||||
|
)
|
||||||
|
else sound_position
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -449,7 +505,7 @@ class CurvableParam(str, Enum):
|
|||||||
"""可曲线化的参数 枚举类"""
|
"""可曲线化的参数 枚举类"""
|
||||||
|
|
||||||
PITCH = "adjust_note_pitch"
|
PITCH = "adjust_note_pitch"
|
||||||
VOLUME = "adjust_note_volume"
|
VOLUME = "adjust_note_volume_weight"
|
||||||
DISTANCE = "adjust_note_sound_distance"
|
DISTANCE = "adjust_note_sound_distance"
|
||||||
LR_PANNING = "adjust_note_leftright_panning_degree"
|
LR_PANNING = "adjust_note_leftright_panning_degree"
|
||||||
UD_PANNING = "adjust_note_updown_panning_degree"
|
UD_PANNING = "adjust_note_updown_panning_degree"
|
||||||
@@ -464,7 +520,7 @@ class SingleTrack(List[SingleNote]):
|
|||||||
is_enabled: bool = True
|
is_enabled: bool = True
|
||||||
"""该音轨是否启用"""
|
"""该音轨是否启用"""
|
||||||
|
|
||||||
instrument: str
|
instrument: Instrument
|
||||||
"""乐器ID"""
|
"""乐器ID"""
|
||||||
|
|
||||||
is_high_time_precision: bool
|
is_high_time_precision: bool
|
||||||
@@ -473,8 +529,14 @@ class SingleTrack(List[SingleNote]):
|
|||||||
is_percussive: bool
|
is_percussive: bool
|
||||||
"""该音轨是否标记为打击乐器轨道"""
|
"""该音轨是否标记为打击乐器轨道"""
|
||||||
|
|
||||||
sound_position: SoundAtmos
|
__volume_weight: float = 10
|
||||||
"""声像方位"""
|
"""音量权"""
|
||||||
|
|
||||||
|
__sound_distance: float = -1
|
||||||
|
"""声源距离"""
|
||||||
|
|
||||||
|
sound_position: Tuple[float, float]
|
||||||
|
"""声像方位 球坐标系角度(rV左右 rH上下)"""
|
||||||
|
|
||||||
argument_curves: Dict[CurvableParam, Union[ParamCurve, Literal[None]]]
|
argument_curves: Dict[CurvableParam, Union[ParamCurve, Literal[None]]]
|
||||||
"""参数曲线"""
|
"""参数曲线"""
|
||||||
@@ -482,20 +544,50 @@ class SingleTrack(List[SingleNote]):
|
|||||||
extra_info: Dict[str, Any]
|
extra_info: Dict[str, Any]
|
||||||
"""你觉得放什么好?"""
|
"""你觉得放什么好?"""
|
||||||
|
|
||||||
|
# 构建类
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*args: SingleNote,
|
*args: SingleNote,
|
||||||
track_name: str = "未命名音轨",
|
track_name: str = "未命名音轨",
|
||||||
track_instrument: str = "",
|
track_instrument: Optional[Instrument] = None,
|
||||||
precise_time: bool = True,
|
precise_time: bool = True,
|
||||||
percussion: bool = False,
|
percussion: bool = False,
|
||||||
sound_direction: Optional[SoundAtmos] = None,
|
volume_weight: float = 10.0,
|
||||||
|
sound_direction: Tuple[float, float] = (0, 0),
|
||||||
extra_information: Dict[str, Any] = {},
|
extra_information: Dict[str, Any] = {},
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
构建音轨
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
==========
|
||||||
|
*args: SingleNote
|
||||||
|
音符列表
|
||||||
|
track_name: str
|
||||||
|
音轨名称
|
||||||
|
track_instrument: str
|
||||||
|
音轨所使用的乐器名称
|
||||||
|
precise_time: bool
|
||||||
|
该音轨是否使用高精度时间
|
||||||
|
percussion: bool
|
||||||
|
该音轨的乐器是否为打击乐器(无音高调整)
|
||||||
|
volume_weight: float
|
||||||
|
音量权,用以表明音轨的音量应当占有的权重大小。值越大,听起来声音越大。
|
||||||
|
默认为 10
|
||||||
|
sound_direction: Tuple[float, float]
|
||||||
|
声源方位
|
||||||
|
注:此参数为tuple,包含两个元素,分别表示:
|
||||||
|
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
||||||
|
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上
|
||||||
|
(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
||||||
|
"""
|
||||||
self.name = track_name
|
self.name = track_name
|
||||||
"""音轨名称"""
|
"""音轨名称"""
|
||||||
|
|
||||||
self.instrument = track_instrument
|
self.instrument = (
|
||||||
|
track_instrument if track_instrument else DefaultInstrument.HARP.value
|
||||||
|
)
|
||||||
"""乐器ID"""
|
"""乐器ID"""
|
||||||
|
|
||||||
self.is_high_time_precision = precise_time
|
self.is_high_time_precision = precise_time
|
||||||
@@ -504,8 +596,13 @@ class SingleTrack(List[SingleNote]):
|
|||||||
self.is_percussive = percussion
|
self.is_percussive = percussion
|
||||||
"""是否为打击乐器"""
|
"""是否为打击乐器"""
|
||||||
|
|
||||||
# 如果不这样的话,所有的新的 SingleTrack 类都会有一个共同的声像方位
|
self.__volume_weight = volume_weight
|
||||||
self.sound_position = sound_direction if sound_direction else SoundAtmos()
|
"""音量权"""
|
||||||
|
|
||||||
|
self.__sound_distance = -1
|
||||||
|
"""声源距离"""
|
||||||
|
|
||||||
|
self.sound_position = sound_direction
|
||||||
"""声像方位"""
|
"""声像方位"""
|
||||||
|
|
||||||
self.extra_info = extra_information if extra_information else {}
|
self.extra_info = extra_information if extra_information else {}
|
||||||
@@ -520,10 +617,11 @@ class SingleTrack(List[SingleNote]):
|
|||||||
cls,
|
cls,
|
||||||
note_list: Iterable[SingleNote],
|
note_list: Iterable[SingleNote],
|
||||||
track_name: str = "未命名音轨",
|
track_name: str = "未命名音轨",
|
||||||
track_instrument: str = "",
|
track_instrument: Optional[Instrument] = None,
|
||||||
precise_time: bool = True,
|
precise_time: bool = True,
|
||||||
percussion: bool = False,
|
percussion: bool = False,
|
||||||
sound_direction: Optional[SoundAtmos] = None,
|
volume_weight: float = 10.0,
|
||||||
|
sound_direction: Tuple[float, float] = (0, 0),
|
||||||
extra_information: Dict[str, Any] = {},
|
extra_information: Dict[str, Any] = {},
|
||||||
) -> "SingleTrack":
|
) -> "SingleTrack":
|
||||||
"""从音符列表创建 SingleTrack 对象"""
|
"""从音符列表创建 SingleTrack 对象"""
|
||||||
@@ -533,12 +631,36 @@ class SingleTrack(List[SingleNote]):
|
|||||||
track_instrument=track_instrument,
|
track_instrument=track_instrument,
|
||||||
precise_time=precise_time,
|
precise_time=precise_time,
|
||||||
percussion=percussion,
|
percussion=percussion,
|
||||||
|
volume_weight=volume_weight,
|
||||||
sound_direction=sound_direction,
|
sound_direction=sound_direction,
|
||||||
extra_information=extra_information,
|
extra_information=extra_information,
|
||||||
)
|
)
|
||||||
single_track.update(note_list)
|
single_track.update(note_list)
|
||||||
return single_track
|
return single_track
|
||||||
|
|
||||||
|
# 类参数
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume_weight(self) -> float:
|
||||||
|
"""音量权"""
|
||||||
|
return self.__volume_weight
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sound_distance(self) -> float:
|
||||||
|
"""声源距离"""
|
||||||
|
if self.__sound_distance < 0:
|
||||||
|
raise ParameterCacheUnfreshError(
|
||||||
|
"未设置声源距离(`{}`),操作时不应直接访问 `__volume_weight` 属性".format(
|
||||||
|
self.__sound_distance
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self.__sound_distance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def note_amount(self) -> int:
|
||||||
|
"""音符数"""
|
||||||
|
return len(self)
|
||||||
|
|
||||||
def disable(self) -> None:
|
def disable(self) -> None:
|
||||||
"""禁用音轨"""
|
"""禁用音轨"""
|
||||||
|
|
||||||
@@ -554,6 +676,36 @@ class SingleTrack(List[SingleNote]):
|
|||||||
|
|
||||||
self.is_enabled = not self.is_enabled
|
self.is_enabled = not self.is_enabled
|
||||||
|
|
||||||
|
def set_volume_weight(
|
||||||
|
self, max_weight: float, minimal_loadness: int, weight
|
||||||
|
) -> float:
|
||||||
|
"""设置音量权
|
||||||
|
|
||||||
|
以下关于距离的计算逻辑请见[计算过程](./resources/响度公式/计算过程.jpg)\n
|
||||||
|
关于响度的计算方法请见[此脚本](./resources/experiments/exam_loadness.py)\n
|
||||||
|
最终公式为:\n
|
||||||
|
声源距离 = 48 - (48 * 音量权 * ( 10 ** ((当前乐器的响度 - 响度最小乐器的响度) / 20 ))) / 全曲最大音量权\n
|
||||||
|
D_t = 48 - \\frac{48 \\cdot P_t \\cdot 10^{\\frac{V_t - V_x}{20}}}{P_m}
|
||||||
|
由于我们存储的响度都是 百-LUFS,所以下面的计算除数为 2000
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.__volume_weight = weight
|
||||||
|
|
||||||
|
self.__sound_distance = volume_weight_to_sound_distance(
|
||||||
|
max_weight, minimal_loadness, weight, self.instrument.loadness
|
||||||
|
)
|
||||||
|
# print(
|
||||||
|
# "收到音量权:",
|
||||||
|
# weight,
|
||||||
|
# ",最大权:",
|
||||||
|
# max_weight,
|
||||||
|
# ",设置声源距离为:",
|
||||||
|
# self.__sound_distance,
|
||||||
|
# )
|
||||||
|
return self.__sound_distance
|
||||||
|
|
||||||
|
# 类操作
|
||||||
|
|
||||||
def append(self, object: SingleNote) -> None:
|
def append(self, object: SingleNote) -> None:
|
||||||
"""
|
"""
|
||||||
添加一个音符,推荐使用 add 方法
|
添加一个音符,推荐使用 add 方法
|
||||||
@@ -594,12 +746,18 @@ class SingleTrack(List[SingleNote]):
|
|||||||
通过时间范围来复制音轨,返回一个音轨
|
通过时间范围来复制音轨,返回一个音轨
|
||||||
"""
|
"""
|
||||||
single_track = SingleTrack.from_note_list(
|
single_track = SingleTrack.from_note_list(
|
||||||
[x for x in self if start_time <= x.precise_start_time <= end_time],
|
(
|
||||||
track_instrument=self.instrument,
|
[x for x in self if start_time <= x.precise_start_time <= end_time]
|
||||||
|
if self.is_high_time_precision
|
||||||
|
else [x for x in self if start_time <= x.start_time <= end_time]
|
||||||
|
),
|
||||||
|
track_instrument=self.instrument.copy(),
|
||||||
precise_time=self.is_high_time_precision,
|
precise_time=self.is_high_time_precision,
|
||||||
percussion=self.is_percussive,
|
percussion=self.is_percussive,
|
||||||
sound_direction=self.sound_position.copy(),
|
volume_weight=self.volume_weight,
|
||||||
|
sound_direction=self.sound_position,
|
||||||
)
|
)
|
||||||
|
single_track.__sound_distance = self.sound_distance
|
||||||
if with_argument_curve:
|
if with_argument_curve:
|
||||||
single_track.argument_curves = {
|
single_track.argument_curves = {
|
||||||
item: (
|
item: (
|
||||||
@@ -616,6 +774,75 @@ class SingleTrack(List[SingleNote]):
|
|||||||
}
|
}
|
||||||
return single_track
|
return single_track
|
||||||
|
|
||||||
|
def paste(
|
||||||
|
self,
|
||||||
|
items: Iterable[SingleNote],
|
||||||
|
offset_time: int,
|
||||||
|
offset_mass_precision_time: int = 0,
|
||||||
|
is_passin_time_precise: bool = True,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
将一个音轨粘贴到当前音轨的指定时间
|
||||||
|
"""
|
||||||
|
note_iterator = items.__iter__()
|
||||||
|
first_note = next(note_iterator)
|
||||||
|
original_start_time = first_note.start_time
|
||||||
|
super().append(
|
||||||
|
first_note.copy(
|
||||||
|
start_tick=offset_time,
|
||||||
|
mass_precision_time=(
|
||||||
|
offset_mass_precision_time if self.is_high_time_precision else 0
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if self.is_high_time_precision:
|
||||||
|
for note in note_iterator:
|
||||||
|
super().append(
|
||||||
|
note.copy(
|
||||||
|
start_tick=note.start_time
|
||||||
|
- original_start_time
|
||||||
|
+ offset_time
|
||||||
|
+ (
|
||||||
|
(
|
||||||
|
offset_mass_precision_time
|
||||||
|
+ note.high_precision_start_time
|
||||||
|
)
|
||||||
|
// 250
|
||||||
|
),
|
||||||
|
mass_precision_time=(
|
||||||
|
offset_mass_precision_time + note.high_precision_start_time
|
||||||
|
)
|
||||||
|
% 250,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if is_passin_time_precise:
|
||||||
|
for note in note_iterator:
|
||||||
|
super().append(
|
||||||
|
note.copy(
|
||||||
|
start_tick=note.start_time
|
||||||
|
- original_start_time
|
||||||
|
+ offset_time
|
||||||
|
+ (
|
||||||
|
(
|
||||||
|
offset_mass_precision_time
|
||||||
|
+ note.high_precision_start_time
|
||||||
|
)
|
||||||
|
// 250
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
for note in note_iterator:
|
||||||
|
super().append(
|
||||||
|
note.copy(
|
||||||
|
start_tick=note.start_time
|
||||||
|
- original_start_time
|
||||||
|
+ offset_time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.sort() # =========================== TODO 需要优化
|
||||||
|
|
||||||
def delete(
|
def delete(
|
||||||
self, start_time: float, end_time: float, with_argument_curve: bool = False
|
self, start_time: float, end_time: float, with_argument_curve: bool = False
|
||||||
):
|
):
|
||||||
@@ -636,6 +863,35 @@ class SingleTrack(List[SingleNote]):
|
|||||||
):
|
):
|
||||||
del self[j - i]
|
del self[j - i]
|
||||||
|
|
||||||
|
# 音符操作
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notes(self) -> List[SingleNote]:
|
||||||
|
"""音符列表"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def minenotes(self) -> Iterator[MineNote]:
|
||||||
|
"""
|
||||||
|
直接返回当前音轨所有音符的我的世界数据形式
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
MineNote.from_single_note(
|
||||||
|
note=_note,
|
||||||
|
note_instrument=self.instrument.name,
|
||||||
|
note_volume_weight=self.volume_weight,
|
||||||
|
is_persiced_time=self.is_high_time_precision,
|
||||||
|
is_percussive_note=self.is_percussive,
|
||||||
|
sound_position=SoundAtmos(self.sound_distance, self.sound_position),
|
||||||
|
**{
|
||||||
|
item.value: argcrv.value_at(_note.precise_start_time)
|
||||||
|
for item in CurvableParam
|
||||||
|
if (argcrv := self.argument_curves[item])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for _note in self
|
||||||
|
)
|
||||||
|
|
||||||
def get(self, time: int) -> Generator[SingleNote, None, None]:
|
def get(self, time: int) -> Generator[SingleNote, None, None]:
|
||||||
"""通过确切的开始时间来获取音符"""
|
"""通过确切的开始时间来获取音符"""
|
||||||
|
|
||||||
@@ -673,10 +929,11 @@ class SingleTrack(List[SingleNote]):
|
|||||||
for _note in self.get_notes(range_start_time, range_end_time):
|
for _note in self.get_notes(range_start_time, range_end_time):
|
||||||
yield MineNote.from_single_note(
|
yield MineNote.from_single_note(
|
||||||
note=_note,
|
note=_note,
|
||||||
note_instrument=self.instrument,
|
note_instrument=self.instrument.name,
|
||||||
|
note_volume_weight=self.volume_weight,
|
||||||
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=SoundAtmos(self.sound_distance, self.sound_position),
|
||||||
**{
|
**{
|
||||||
item.value: argcrv.value_at(_note.precise_start_time)
|
item.value: argcrv.value_at(_note.precise_start_time)
|
||||||
for item in CurvableParam
|
for item in CurvableParam
|
||||||
@@ -684,36 +941,7 @@ class SingleTrack(List[SingleNote]):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
# 其他
|
||||||
def note_amount(self) -> int:
|
|
||||||
"""音符数"""
|
|
||||||
return len(self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def notes(self) -> List[SingleNote]:
|
|
||||||
"""音符列表"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
@property
|
|
||||||
def minenotes(self) -> Iterator[MineNote]:
|
|
||||||
"""
|
|
||||||
直接返回当前音轨所有音符的我的世界数据形式
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
MineNote.from_single_note(
|
|
||||||
note=_note,
|
|
||||||
note_instrument=self.instrument,
|
|
||||||
is_persiced_time=self.is_high_time_precision,
|
|
||||||
is_percussive_note=self.is_percussive,
|
|
||||||
sound_position=self.sound_position,
|
|
||||||
**{
|
|
||||||
item.value: argcrv.value_at(_note.precise_start_time)
|
|
||||||
for item in CurvableParam
|
|
||||||
if (argcrv := self.argument_curves[item])
|
|
||||||
},
|
|
||||||
)
|
|
||||||
for _note in self
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||||
"""设置附加信息"""
|
"""设置附加信息"""
|
||||||
@@ -759,6 +987,31 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
extra_info: Dict[str, Any]
|
extra_info: Dict[str, Any]
|
||||||
"""这还得放东西?"""
|
"""这还得放东西?"""
|
||||||
|
|
||||||
|
__max_volume_weight: float = -1
|
||||||
|
"""全曲最大音量权"""
|
||||||
|
|
||||||
|
__minimal_loadness: int = 1
|
||||||
|
"""全曲响度最小的乐器采样的响度"""
|
||||||
|
|
||||||
|
# 前置方法
|
||||||
|
|
||||||
|
def _calc_values(self):
|
||||||
|
for track in self:
|
||||||
|
self.__max_volume_weight = max(
|
||||||
|
track.volume_weight, self.__max_volume_weight
|
||||||
|
)
|
||||||
|
self.__minimal_loadness = min(
|
||||||
|
track.instrument.loadness, self.__minimal_loadness
|
||||||
|
)
|
||||||
|
for track in self:
|
||||||
|
track.set_volume_weight(
|
||||||
|
max_weight=self.__max_volume_weight,
|
||||||
|
minimal_loadness=self.__minimal_loadness,
|
||||||
|
weight=track.volume_weight,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 类的构建方法
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*args: SingleTrack,
|
*args: SingleTrack,
|
||||||
@@ -788,6 +1041,8 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
|
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
|
||||||
|
self._calc_values()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_track_list(
|
def from_track_list(
|
||||||
cls,
|
cls,
|
||||||
@@ -810,6 +1065,95 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
single_music.extend(track_list)
|
single_music.extend(track_list)
|
||||||
return single_music
|
return single_music
|
||||||
|
|
||||||
|
# 类操作
|
||||||
|
|
||||||
|
def append(self, track: SingleTrack) -> None:
|
||||||
|
"""
|
||||||
|
将一个音轨添加到全曲中,排列在所有音轨之后。
|
||||||
|
|
||||||
|
Append object to the end of the list.
|
||||||
|
"""
|
||||||
|
super().append(track)
|
||||||
|
self._calc_values()
|
||||||
|
|
||||||
|
def extend(self, tracks: Sequence[SingleTrack]) -> None:
|
||||||
|
"""
|
||||||
|
将一个音轨列表添加到全曲中,依顺序在所有音轨之后排列。
|
||||||
|
|
||||||
|
Extend list by appending elements from the iterable.
|
||||||
|
"""
|
||||||
|
super().extend(tracks)
|
||||||
|
self._calc_values()
|
||||||
|
|
||||||
|
def insert(self, index: int, track: SingleTrack) -> None:
|
||||||
|
"""
|
||||||
|
将一个音轨插入到全曲中,在指定位置。
|
||||||
|
|
||||||
|
Insert object before index.
|
||||||
|
"""
|
||||||
|
super().insert(index, track)
|
||||||
|
self._calc_values()
|
||||||
|
|
||||||
|
def remove(self, track: SingleTrack) -> None:
|
||||||
|
"""
|
||||||
|
从全曲中删除一个音轨。
|
||||||
|
|
||||||
|
Remove first occurrence of value.
|
||||||
|
|
||||||
|
当音轨不存在时,会抛出 `ValueError`。
|
||||||
|
Raises `ValueError` if the value is not present.
|
||||||
|
"""
|
||||||
|
super().remove(track)
|
||||||
|
self._calc_values()
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""
|
||||||
|
清空全曲。
|
||||||
|
|
||||||
|
Remove all items from list.
|
||||||
|
"""
|
||||||
|
super().clear()
|
||||||
|
self.__max_volume_weight = -1
|
||||||
|
self.__minimal_loadness = 1
|
||||||
|
|
||||||
|
def pop(self, index: SupportsIndex = -1) -> SingleTrack:
|
||||||
|
"""
|
||||||
|
从全曲中删除一个音轨。
|
||||||
|
|
||||||
|
Remove and return item at index (default last).
|
||||||
|
|
||||||
|
当音轨不存在时,会抛出 `IndexError`。
|
||||||
|
|
||||||
|
Raises IndexError if list is empty or index is out of range.
|
||||||
|
"""
|
||||||
|
to_pop = super().pop(index)
|
||||||
|
self._calc_values()
|
||||||
|
return to_pop
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __setitem__(self, key: SupportsIndex, value: SingleTrack) -> None: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __setitem__(self, key: slice, value: Sequence[SingleTrack]) -> None: ...
|
||||||
|
|
||||||
|
def __setitem__(self, key, value) -> None:
|
||||||
|
"""
|
||||||
|
设置一个音轨
|
||||||
|
|
||||||
|
Set self[key] to value.
|
||||||
|
"""
|
||||||
|
super().__setitem__(key, value)
|
||||||
|
self._calc_values()
|
||||||
|
|
||||||
|
def __delitem__(self, key: SupportsIndex | slice) -> None:
|
||||||
|
"""
|
||||||
|
删除一个音轨
|
||||||
|
|
||||||
|
Delete self[key].
|
||||||
|
"""
|
||||||
|
super().__delitem__(key)
|
||||||
|
self._calc_values()
|
||||||
|
|
||||||
def copy(self) -> "SingleMusic":
|
def copy(self) -> "SingleMusic":
|
||||||
|
|
||||||
# 没想到真的会用到全曲复制……
|
# 没想到真的会用到全曲复制……
|
||||||
@@ -827,6 +1171,8 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
credits=self.music_credits,
|
credits=self.music_credits,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 类属性
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def track_amount(self) -> int:
|
def track_amount(self) -> int:
|
||||||
"""音轨数"""
|
"""音轨数"""
|
||||||
@@ -837,6 +1183,36 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
"""音轨列表,不包含被禁用的音轨"""
|
"""音轨列表,不包含被禁用的音轨"""
|
||||||
return (track for track in self if track.is_enabled)
|
return (track for track in self if track.is_enabled)
|
||||||
|
|
||||||
|
def set_loadness(self, track: Union[int, SingleTrack], weight: float):
|
||||||
|
if weight > self.__max_volume_weight:
|
||||||
|
if isinstance(track, int):
|
||||||
|
self[track].__volume_weight = weight
|
||||||
|
elif isinstance(track, SingleTrack):
|
||||||
|
if track in self:
|
||||||
|
track.__volume_weight = weight
|
||||||
|
else:
|
||||||
|
raise ParameterValueError(
|
||||||
|
"音轨:`{}`不属于此曲目".format(track.name)
|
||||||
|
)
|
||||||
|
self._calc_values()
|
||||||
|
else:
|
||||||
|
if isinstance(track, int):
|
||||||
|
self[track].set_volume_weight(
|
||||||
|
max_weight=self.__max_volume_weight,
|
||||||
|
minimal_loadness=self.__minimal_loadness,
|
||||||
|
weight=weight,
|
||||||
|
)
|
||||||
|
elif isinstance(track, SingleTrack):
|
||||||
|
track.set_volume_weight(
|
||||||
|
max_weight=self.__max_volume_weight,
|
||||||
|
minimal_loadness=self.__minimal_loadness,
|
||||||
|
weight=weight,
|
||||||
|
)
|
||||||
|
|
||||||
|
raise ParameterTypeError("音轨不得为`{}`类型".format(type(track)))
|
||||||
|
|
||||||
|
# 音符操作
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def yield_from_tracks(
|
def yield_from_tracks(
|
||||||
tracks: Sequence[Iterator[T]],
|
tracks: Sequence[Iterator[T]],
|
||||||
@@ -938,6 +1314,8 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
sort_key=lambda x: x.start_tick,
|
sort_key=lambda x: x.start_tick,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 其他
|
||||||
|
|
||||||
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||||
"""设置附加信息"""
|
"""设置附加信息"""
|
||||||
if isinstance(key, str):
|
if isinstance(key, str):
|
||||||
|
|||||||
@@ -73,6 +73,15 @@ class InnerlyParameterError(MusicreaterInnerlyError):
|
|||||||
super().__init__("传参错误 - ", *args)
|
super().__init__("传参错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class ParameterCacheUnfreshError(InnerlyParameterError):
|
||||||
|
"""参数缓存未刷新"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""参数缓存未刷新"""
|
||||||
|
super().__init__("参数缓存未刷新:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ParameterTypeError(InnerlyParameterError, TypeError):
|
class ParameterTypeError(InnerlyParameterError, TypeError):
|
||||||
"""参数类型错误"""
|
"""参数类型错误"""
|
||||||
|
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ class ParamCurve:
|
|||||||
if idx < len(self._keys) and self._keys[idx].time == time:
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
return idx, self._keys[idx]
|
return idx, self._keys[idx]
|
||||||
else:
|
else:
|
||||||
print("[警告] ParamCurve.find_key: 找不到指定时间点所对应之关键帧")
|
# print("[警告] ParamCurve.find_key: 找不到指定时间点所对应之关键帧")
|
||||||
return idx, None
|
return idx, None
|
||||||
|
|
||||||
def copy(
|
def copy(
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
import math
|
||||||
import random
|
import random
|
||||||
|
|
||||||
# from io import BytesIO
|
# from io import BytesIO
|
||||||
@@ -48,6 +49,52 @@ from .subclass import MineNote, mctick2timestr, SingleNoteBox
|
|||||||
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def volume_2_distance_natural(
|
||||||
|
vol: float,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
Midi 力度值/音量值拟合成的距离函数,一种更加自然的听感?
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
vol: int
|
||||||
|
Midi 音符力度值(0~127)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float播放中心到玩家的距离
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
-8.081720684086314
|
||||||
|
* math.log(
|
||||||
|
vol + 14.579508825070013,
|
||||||
|
)
|
||||||
|
+ 37.65806375944386
|
||||||
|
if vol < 60.64
|
||||||
|
else 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649)
|
||||||
|
+ -6.313841334963396 * (vol + 2592.272889454798)
|
||||||
|
+ 4558.496367823575
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def volume_2_distance_straight(vol: float) -> float:
|
||||||
|
"""
|
||||||
|
Midi 力度值/音量值拟合成的距离函数,线性转换
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
vol: int
|
||||||
|
Midi 音符力度值(0~127)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float播放中心到玩家的距离
|
||||||
|
"""
|
||||||
|
return (vol + 1) / -8 + 16
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def inst_to_sould_with_deviation(
|
def inst_to_sould_with_deviation(
|
||||||
instrumentID: int,
|
instrumentID: int,
|
||||||
reference_table: MidiInstrumentTableType,
|
reference_table: MidiInstrumentTableType,
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1003 KiB |
Binary file not shown.
Reference in New Issue
Block a user