小重构:音量vs距离新的计算方案

This commit is contained in:
2026-07-22 08:23:16 +08:00
parent df3519ebc5
commit debd42c633
15 changed files with 673 additions and 217 deletions
+4
View File
@@ -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__/
+6 -1
View File
@@ -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",
] ]
+44
View File
@@ -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 相关默认值
+45 -21
View File
@@ -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,8 +420,7 @@ 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))
@@ -421,30 +439,32 @@ class MidiImport2MusicPlugin(MusicInputPluginBase):
if _is_percussion if _is_percussion
else config.pitched_note_reference_table else config.pitched_note_reference_table
), ),
volume_processing_method=config.volume_process_function,
panning_processing_method=config.panning_processing_function, panning_processing_method=config.panning_processing_function,
note_table_replacement=config.note_replacement_table, note_table_replacement=config.note_replacement_table,
lyric_line=( lyric_line=(
_lyric.encode("latin1").decode( _lyric.encode("latin1").decode(config.string_encoding)
config.string_encoding
)
if config.string_encoding if config.string_encoding
else _lyric 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
+2 -51
View File
@@ -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
+14 -7
View File
@@ -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
View File
@@ -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):
+9
View File
@@ -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):
"""参数类型错误""" """参数类型错误"""
+1 -1
View File
@@ -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(
+47
View File
@@ -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.