mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-07-27 18:52:33 +00:00
完成音乐合成插件之迁移
This commit is contained in:
+7
-2
@@ -6,8 +6,6 @@
|
||||
# mystuff
|
||||
/*.zip
|
||||
/.vscode
|
||||
/*.mid
|
||||
/*.midi
|
||||
/*.mcpack
|
||||
/*.bdx
|
||||
/*.msq
|
||||
@@ -28,6 +26,13 @@ RES.txt
|
||||
/fcwslib
|
||||
test_lyric-mido.py
|
||||
test_doing_things.py
|
||||
TEST-*.*
|
||||
|
||||
# Copyrighted
|
||||
vanilla_assets/
|
||||
/*.mid
|
||||
/*.midi
|
||||
|
||||
|
||||
# Byte-compiled / optimized
|
||||
__pycache__/
|
||||
|
||||
@@ -87,7 +87,7 @@ class McstructureExportConfig(PluginConfig):
|
||||
class MusicExportToMcstructureWithDelayPlayerPlugin(MusicOutputPluginBase):
|
||||
|
||||
metainfo = PluginMetaInformation(
|
||||
name="导出全曲结构插件(mcstructure结构、延迟播放器)",
|
||||
name="导出全曲至结构(mcstructure结构、延迟播放器)",
|
||||
author="金羿、玉衡Alioth",
|
||||
description="将音·创 v3 的整首音乐数据,以指令方块延迟的播放形式,导出为基岩版 MCSTRUCTURE 结构",
|
||||
version=(0, 0, 1),
|
||||
|
||||
@@ -177,7 +177,7 @@ class MidiImport2MusicPlugin(MusicInputPluginBase):
|
||||
"""Midi 音乐数据导入插件"""
|
||||
|
||||
metainfo = PluginMetaInformation(
|
||||
name="Midi 导入插件",
|
||||
name="从 Midi 导入全曲",
|
||||
author="金羿、玉衡Alioth",
|
||||
description="从 Midi 文件导入音乐数据",
|
||||
version=(0, 0, 1),
|
||||
|
||||
@@ -31,3 +31,119 @@ limitations under the License.
|
||||
# 本插件依照 Apache 2.0 协议开放源代码,若需转载或借鉴
|
||||
# 许可声明请查看 http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from time import perf_counter_ns
|
||||
from dataclasses import dataclass
|
||||
from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple, Literal
|
||||
|
||||
|
||||
from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos, MineNote
|
||||
from Musicreater.plugins import (
|
||||
PluginConfig,
|
||||
PluginMetaInformation,
|
||||
PluginTypes,
|
||||
music_output_plugin,
|
||||
MusicOutputPluginBase,
|
||||
track_output_plugin,
|
||||
TrackOutputPluginBase,
|
||||
)
|
||||
|
||||
|
||||
from .main import MusicPreview
|
||||
|
||||
|
||||
@dataclass
|
||||
class PcmConversionConfig(PluginConfig):
|
||||
|
||||
assets_path: Path
|
||||
"""
|
||||
生成音频文件所使用的资源文件路径
|
||||
"""
|
||||
|
||||
synthesis_mode: Literal[0, 1, 2, 3, 4] = 1
|
||||
"""
|
||||
音频合成模式
|
||||
- 0 原始长度,不变化时长
|
||||
- 1 拉伸至 mc 播放器定义(我的世界效果)
|
||||
- 2 根据 midi 音符长度裁剪
|
||||
- 3 混音预留
|
||||
- 4 匹配 midi 音符长度(最佳效果)
|
||||
"""
|
||||
overlay_mode: Literal[1, 2] = 1
|
||||
"""
|
||||
没看懂这个参数的意思
|
||||
"""
|
||||
sample_rate: int = 44100
|
||||
"""
|
||||
目标输出的采样率
|
||||
"""
|
||||
value_get_method: Literal[0, 1] = 1
|
||||
"""
|
||||
采样取值方法,没看懂什么意思
|
||||
- 0 均值化
|
||||
- 1 钳制位
|
||||
"""
|
||||
|
||||
pitch_accuracy_decimals: int = 0
|
||||
"""
|
||||
音调处理精度,小数点后位数
|
||||
"""
|
||||
|
||||
global_deviation: float = 0
|
||||
"""
|
||||
全曲音调偏移
|
||||
"""
|
||||
|
||||
|
||||
@music_output_plugin("music_to_pcm_plugin")
|
||||
class NoteDataConvert2PcmPlugin(MusicOutputPluginBase):
|
||||
metainfo = PluginMetaInformation(
|
||||
name="全曲预览播放·PCM",
|
||||
author="金羿、鱼旧梦",
|
||||
description="从全曲的音符数据转换为可以用于播放的 PCM 编码数据",
|
||||
version=(0, 0, 1),
|
||||
type=PluginTypes.FUNCTION_MUSIC_EXPORT,
|
||||
license="Apache 2.0",
|
||||
)
|
||||
|
||||
def dump(self, data: SingleMusic, file_path: Path, config: PcmConversionConfig):
|
||||
|
||||
pr = perf_counter_ns()
|
||||
music_preview = MusicPreview(
|
||||
resource_folder=config.assets_path,
|
||||
synthesis_mode=config.synthesis_mode,
|
||||
overlay_mode=config.overlay_mode,
|
||||
sample_rate=config.sample_rate,
|
||||
value_get_method=config.value_get_method,
|
||||
pitch_accuracy_decimals=config.pitch_accuracy_decimals,
|
||||
music_deviation=config.global_deviation,
|
||||
)
|
||||
music_preview.to_wav_file(data, file_path)
|
||||
af = perf_counter_ns()
|
||||
print("合成用时", af - pr, "纳秒,即", (af - pr) / 1000_000_000, "秒")
|
||||
|
||||
def stream_dump(
|
||||
self, data: SingleMusic, config: PcmConversionConfig
|
||||
) -> Iterator[bytes]:
|
||||
|
||||
pr = perf_counter_ns()
|
||||
music_preview = MusicPreview(
|
||||
resource_folder=config.assets_path,
|
||||
synthesis_mode=config.synthesis_mode,
|
||||
overlay_mode=config.overlay_mode,
|
||||
sample_rate=config.sample_rate,
|
||||
value_get_method=config.value_get_method,
|
||||
pitch_accuracy_decimals=config.pitch_accuracy_decimals,
|
||||
music_deviation=config.global_deviation,
|
||||
)
|
||||
|
||||
b_out = BytesIO()
|
||||
|
||||
music_preview.to_wav_file_byte(data, b_out)
|
||||
af = perf_counter_ns()
|
||||
print("合成用时", af - pr, "纳秒,即", (af - pr) / 1000_000_000, "秒")
|
||||
|
||||
b_out.seek(0)
|
||||
|
||||
yield from b_out
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
音·创 v3 内置的 音乐预览插件
|
||||
音·创 v3 内置的 音乐合成预览功能
|
||||
"""
|
||||
|
||||
"""
|
||||
@@ -33,8 +33,10 @@ limitations under the License.
|
||||
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from typing import Any, Union
|
||||
|
||||
from math import inf
|
||||
from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple, Literal
|
||||
from pathlib import Path
|
||||
|
||||
import soundfile as sf
|
||||
import librosa
|
||||
@@ -42,31 +44,345 @@ import numpy as np
|
||||
|
||||
|
||||
from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos, MineNote
|
||||
from Musicreater.plugins import (
|
||||
library_plugin,
|
||||
PluginConfig,
|
||||
PluginMetaInformation,
|
||||
PluginTypes,
|
||||
LibraryPluginBase,
|
||||
)
|
||||
from Musicreater.exceptions import ZeroSpeedError, IllegalMinimumVolumeError
|
||||
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||
from Musicreater.constants import MM_INSTRUMENT_DEVIATION_TABLE
|
||||
|
||||
|
||||
class MusicPreview:
|
||||
"""
|
||||
原始资源存储规范:
|
||||
Dict[str"ID1": Dict[str"SoundID": ndarray[]]]
|
||||
转换资源存储规范:
|
||||
Dict[str"ID1": Dict[str"SoundID": Dict[int"Pitch": ndarray]]]
|
||||
测试阶段,请直接vanilla作为默认传入,不考虑载入
|
||||
"""
|
||||
|
||||
res_raw_data: dict = {} # 原始资源存储
|
||||
# res_raw_index: list = [] # 资源转换情况,key: [88] 留着优化
|
||||
res_data: dict = {} # 转换后资源存储
|
||||
res_data_sr: int # 转换后采样率,和输出采样率一致
|
||||
|
||||
@staticmethod
|
||||
def res_pitch_shift(y: np.ndarray, *, sr: float, n_steps: float, **kwargs: Any):
|
||||
return librosa.effects.pitch_shift(y, sr=sr, n_steps=n_steps)
|
||||
|
||||
@staticmethod
|
||||
def res_time_stretch(y: np.ndarray, *, rate: float, **kwargs: Any):
|
||||
return librosa.effects.time_stretch(y, rate=rate)
|
||||
|
||||
@staticmethod
|
||||
def res_resample(
|
||||
y: np.ndarray,
|
||||
*,
|
||||
orig_sr: float,
|
||||
target_sr: float,
|
||||
fix: bool = True,
|
||||
**kwargs: Any,
|
||||
):
|
||||
return librosa.resample(y, orig_sr=orig_sr, target_sr=target_sr, fix=fix)
|
||||
|
||||
@library_plugin("notedata_to_pcm_plugin")
|
||||
class NoteDataConvert2PcmPlugin(LibraryPluginBase):
|
||||
metainfo = PluginMetaInformation(
|
||||
name="音符数据预览播放支持插件·PCM",
|
||||
author="金羿、鱼旧梦",
|
||||
description="从音符数据转换为可以用于播放的 PCM 编码数据",
|
||||
version=(0, 0, 1),
|
||||
type=PluginTypes.LIBRARY,
|
||||
license="Apache 2.0",
|
||||
)
|
||||
def __init__(
|
||||
self,
|
||||
resource_folder: Path,
|
||||
synthesis_mode: Literal[0, 1, 2, 3, 4] = 1,
|
||||
overlay_mode: Literal[1, 2] = 1,
|
||||
sample_rate: int = 44100,
|
||||
value_get_method: Literal[0, 1] = 1,
|
||||
pitch_accuracy_decimals: int = 0,
|
||||
music_deviation: float = 0,
|
||||
):
|
||||
|
||||
self.mode = synthesis_mode
|
||||
self.overlay_mode_c = overlay_mode
|
||||
self.output_sample_rate = sample_rate
|
||||
self.get_value_method = value_get_method
|
||||
self.resources_path = resource_folder
|
||||
self.pitch_precision_decimals = pitch_accuracy_decimals
|
||||
|
||||
self.pitch_deviation = music_deviation
|
||||
|
||||
for file in self.resources_path.iterdir():
|
||||
if file.is_file():
|
||||
self.res_raw_data[file.stem] = librosa.load(file, sr=None)
|
||||
|
||||
def res_shift(
|
||||
self,
|
||||
sound_name: str,
|
||||
percussive: bool,
|
||||
pitch: int,
|
||||
duration: int,
|
||||
# rate: int
|
||||
):
|
||||
y_orig, sr_orig = self.res_raw_data[sound_name]
|
||||
if not percussive:
|
||||
if self.mode == 1:
|
||||
# 变调, 时域压扩, 重采样 mc方法
|
||||
self.res_data[sound_name][pitch] = librosa.resample(
|
||||
librosa.effects.time_stretch(
|
||||
librosa.effects.pitch_shift(y_orig, sr=sr_orig, n_steps=pitch),
|
||||
rate=2 ** (pitch / 12),
|
||||
),
|
||||
orig_sr=sr_orig,
|
||||
target_sr=self.output_sample_rate,
|
||||
fix=False,
|
||||
)
|
||||
elif self.mode == 0:
|
||||
# 重采样, 变调
|
||||
self.res_data[sound_name][pitch] = librosa.resample(
|
||||
librosa.effects.pitch_shift(y_orig, sr=sr_orig, n_steps=pitch),
|
||||
orig_sr=sr_orig,
|
||||
target_sr=self.output_sample_rate,
|
||||
fix=False,
|
||||
)
|
||||
elif self.mode == 4:
|
||||
|
||||
# 变调, 时域压扩, 重采样 MIDI-FFT
|
||||
if self.overlay_mode_c == 2:
|
||||
rate = duration / 20 / (len(y_orig[0]) / sr_orig)
|
||||
else:
|
||||
rate = duration / 20 / (len(y_orig) / sr_orig)
|
||||
rate = rate if rate != 0 else 1
|
||||
self.res_data[sound_name][pitch] = librosa.resample(
|
||||
librosa.effects.time_stretch(
|
||||
librosa.effects.pitch_shift(y_orig, sr=sr_orig, n_steps=pitch),
|
||||
rate=rate,
|
||||
),
|
||||
orig_sr=sr_orig,
|
||||
target_sr=self.output_sample_rate,
|
||||
fix=False,
|
||||
)
|
||||
elif self.mode == 2:
|
||||
# 变调, 时域压扩, 重采样 MIDI-cut
|
||||
if self.overlay_mode_c == 2:
|
||||
deal = librosa.effects.pitch_shift(
|
||||
y_orig, sr=sr_orig, n_steps=pitch
|
||||
)[
|
||||
...,
|
||||
: (
|
||||
int(duration / 20 * sr_orig)
|
||||
if duration / 20 * sr_orig > len(y_orig[0])
|
||||
else len(y_orig[0])
|
||||
),
|
||||
]
|
||||
else:
|
||||
deal = librosa.effects.pitch_shift(
|
||||
y_orig, sr=sr_orig, n_steps=pitch
|
||||
)[
|
||||
: (
|
||||
int(duration / 20 * sr_orig)
|
||||
if duration / 20 * sr_orig > len(y_orig)
|
||||
else len(y_orig)
|
||||
)
|
||||
]
|
||||
self.res_data[sound_name][pitch] = librosa.resample(
|
||||
deal, orig_sr=sr_orig, target_sr=self.output_sample_rate, fix=False
|
||||
)
|
||||
else:
|
||||
# if self.mode == 1:
|
||||
# 重采样, 不变调
|
||||
print(">>", sound_name, pitch)
|
||||
self.res_data[sound_name][pitch] = librosa.resample(
|
||||
y_orig,
|
||||
orig_sr=sr_orig,
|
||||
target_sr=self.output_sample_rate,
|
||||
fix=False, # 尽管这样不变调,但是也不应将 pitch 与打击乐器联系在一起
|
||||
)
|
||||
"""elif self.mode == 0:
|
||||
# 重采样, 不变调, 衰弱
|
||||
self.cache_dict[raw_name] = librosa_resample(
|
||||
y_orig,
|
||||
orig_sr=sr_orig,
|
||||
target_sr=self.out_sr,
|
||||
fix=False
|
||||
)"""
|
||||
|
||||
def make_pitch_accurate(self, pitch: float) -> int:
|
||||
"""
|
||||
相信你知道的,小数并不准确
|
||||
"""
|
||||
return int((pitch + self.pitch_deviation) * (10**self.pitch_precision_decimals))
|
||||
|
||||
# 获取资源方法,需要指定采样率,采样率变了即全局重载
|
||||
# 资源存储类里面有就取,没就写入并标识已有数据
|
||||
def get_res(
|
||||
self,
|
||||
sound_name: str,
|
||||
pitch: float,
|
||||
percussive: bool,
|
||||
duration: int,
|
||||
):
|
||||
|
||||
print(
|
||||
sound_name,
|
||||
pitch,
|
||||
">>",
|
||||
(pitch := self.make_pitch_accurate(pitch)),
|
||||
percussive,
|
||||
duration,
|
||||
)
|
||||
|
||||
if sound_name in self.res_data:
|
||||
if pitch in self.res_data[sound_name]:
|
||||
return self.res_data[sound_name][pitch]
|
||||
else:
|
||||
self.res_shift(
|
||||
sound_name=sound_name,
|
||||
percussive=percussive,
|
||||
pitch=pitch,
|
||||
duration=duration,
|
||||
)
|
||||
# print(2, self.res_data[packageID][soundID][pitch])
|
||||
return self.res_data[sound_name][pitch]
|
||||
else:
|
||||
self.res_data[sound_name] = {}
|
||||
self.res_shift(
|
||||
sound_name=sound_name,
|
||||
percussive=percussive,
|
||||
pitch=pitch,
|
||||
duration=duration,
|
||||
)
|
||||
# print(2, self.res_data[packageID][soundID][pitch])
|
||||
return self.res_data[sound_name][pitch]
|
||||
|
||||
def convert(self, music: SingleMusic, start_tick: float = 0, end_tick: float = inf):
|
||||
|
||||
if self.overlay_mode_c == 1:
|
||||
|
||||
def overlay(seg_overlay: np.ndarray, pos_tick: int):
|
||||
pos_ = int(out_sr * pos_tick * 0.05)
|
||||
wav_model[pos_ : seg_overlay.size + pos_] += seg_overlay
|
||||
|
||||
# print(0)
|
||||
wav_model = np.zeros(
|
||||
int(
|
||||
max(
|
||||
[
|
||||
(
|
||||
i.start_tick * 0.05 * self.output_sample_rate
|
||||
+ len(
|
||||
self.get_res(
|
||||
sound_name=i.instrument,
|
||||
pitch=i.pitch
|
||||
- 60
|
||||
- MM_INSTRUMENT_DEVIATION_TABLE.get(
|
||||
i.instrument, 6
|
||||
),
|
||||
percussive=i.percussive,
|
||||
duration=i.duration_tick,
|
||||
)
|
||||
)
|
||||
)
|
||||
for i in music.get_minenotes(
|
||||
start_time=start_tick, end_time=end_tick
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
dtype=np.float32,
|
||||
)
|
||||
|
||||
elif self.overlay_mode_c == 2:
|
||||
|
||||
def overlay(seg_overlay: np.ndarray, pos_tick: int):
|
||||
pos_ = int(out_sr * pos_tick * 0.05)
|
||||
wav_model[..., pos_ : len(seg_overlay[0]) + pos_] += seg_overlay
|
||||
|
||||
wav_model = np.zeros(
|
||||
(
|
||||
2,
|
||||
int(
|
||||
max(
|
||||
[
|
||||
(
|
||||
i.start_tick * 0.05 * self.output_sample_rate
|
||||
+ len(
|
||||
self.get_res(
|
||||
sound_name=i.instrument,
|
||||
pitch=i.pitch
|
||||
- 60
|
||||
- MM_INSTRUMENT_DEVIATION_TABLE.get(
|
||||
i.instrument, 6
|
||||
),
|
||||
percussive=i.percussive,
|
||||
duration=i.duration_tick,
|
||||
)
|
||||
)
|
||||
)
|
||||
for i in music.get_minenotes(
|
||||
start_time=start_tick, end_time=end_tick
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
),
|
||||
dtype=np.float32,
|
||||
)
|
||||
|
||||
else:
|
||||
raise ValueError("错误的 overlay_mode 参数")
|
||||
|
||||
out_sr = self.output_sample_rate
|
||||
|
||||
for note in music.get_minenotes(start_time=start_tick, end_time=end_tick):
|
||||
accurate_pitch = self.make_pitch_accurate(
|
||||
note.pitch
|
||||
- 60
|
||||
- (
|
||||
0
|
||||
if note.percussive
|
||||
else MM_INSTRUMENT_DEVIATION_TABLE.get(note.instrument, 6)
|
||||
)
|
||||
)
|
||||
print(
|
||||
":",
|
||||
note.instrument,
|
||||
note.pitch,
|
||||
">>",
|
||||
accurate_pitch,
|
||||
note.percussive,
|
||||
note.duration_tick,
|
||||
)
|
||||
if not note.percussive:
|
||||
overlay(
|
||||
self.res_data[note.instrument][accurate_pitch] * note.volume / 127,
|
||||
note.start_tick,
|
||||
)
|
||||
else:
|
||||
overlay(
|
||||
self.res_data[note.instrument][accurate_pitch] * note.volume / 127,
|
||||
note.start_tick,
|
||||
)
|
||||
|
||||
if self.get_value_method == 0:
|
||||
# 归一化,抚摸耳朵 (bushi
|
||||
max_val = np.max(np.abs(wav_model))
|
||||
if not max_val == 0:
|
||||
wav_model = wav_model / max_val
|
||||
elif self.get_value_method == 1:
|
||||
wav_model[wav_model > 1] = 1
|
||||
wav_model[wav_model < -1] = -1
|
||||
if self.overlay_mode_c == 2:
|
||||
return wav_model.T
|
||||
else:
|
||||
return wav_model[:, np.newaxis]
|
||||
|
||||
def stream(self, music: SingleMusic):
|
||||
pass
|
||||
|
||||
# debug
|
||||
def to_wav_file(self, single_music: SingleMusic, out_file_path: Path):
|
||||
|
||||
sf.write(
|
||||
out_file_path,
|
||||
self.convert(single_music),
|
||||
samplerate=self.output_sample_rate,
|
||||
format="wav",
|
||||
)
|
||||
return out_file_path
|
||||
|
||||
def to_wav_file_byte(self, single_music: SingleMusic, output_file: BinaryIO):
|
||||
sf.write(
|
||||
output_file,
|
||||
self.convert(single_music),
|
||||
samplerate=self.output_sample_rate,
|
||||
format="wav",
|
||||
)
|
||||
|
||||
@@ -96,7 +96,7 @@ class MineCommand:
|
||||
@library_plugin("notedata_to_command_plugin")
|
||||
class NoteDataConvert2CommandPlugin(LibraryPluginBase):
|
||||
metainfo = PluginMetaInformation(
|
||||
name="音符数据指令支持插件",
|
||||
name="音符数据的基岩版指令支持",
|
||||
author="金羿、玉衡Alioth",
|
||||
description="从音符数据转换为我的世界指令相关格式",
|
||||
version=(0, 0, 1),
|
||||
|
||||
+15
-10
@@ -174,7 +174,7 @@ class SingleNote:
|
||||
midi_pitch: int
|
||||
Midi 音高
|
||||
note_volume: int
|
||||
响度/力度(百廿七分比, 0~127)
|
||||
响度或曰力度(百廿七分比, 0~127)
|
||||
start_time: int
|
||||
开始之时(命令刻)
|
||||
注:此处的时间是用从乐曲开始到当前的刻数
|
||||
@@ -377,9 +377,9 @@ class MineNote:
|
||||
pitch: float
|
||||
"""Midi 音高"""
|
||||
instrument: str
|
||||
"""乐器 ID"""
|
||||
"""乐器标识"""
|
||||
volume: float
|
||||
"""力度/播放音量 0~127 百廿七分比"""
|
||||
"""力度或曰播放音量、响度 0~127 百廿七分比"""
|
||||
start_tick: int
|
||||
"""开始之时 命令刻"""
|
||||
duration_tick: int
|
||||
@@ -387,7 +387,7 @@ class MineNote:
|
||||
start_time_offset: int
|
||||
"""高精度开始时间偏量 1/5000 秒"""
|
||||
percussive: bool
|
||||
"""是否作为打击乐器启用"""
|
||||
"""是否作为打击乐器使用"""
|
||||
position: SoundAtmos
|
||||
"""声像方位"""
|
||||
|
||||
@@ -619,7 +619,7 @@ class SingleTrack(List[SingleNote]):
|
||||
return (x for x in self if x.precise_start_time == time)
|
||||
|
||||
def get_notes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
self, start_time: float = 0, end_time: float = inf
|
||||
) -> Iterator[SingleNote]:
|
||||
"""通过开始时间和结束时间来获取音符"""
|
||||
if end_time < start_time:
|
||||
@@ -791,7 +791,12 @@ class SingleMusic(List[SingleTrack]):
|
||||
|
||||
# 没想到真的会用到全曲复制……
|
||||
return self.from_track_list(
|
||||
track_list=[tr.copy(with_argument_curve=True,) for tr in self],
|
||||
track_list=[
|
||||
tr.copy(
|
||||
with_argument_curve=True,
|
||||
)
|
||||
for tr in self
|
||||
],
|
||||
name=self.music_name,
|
||||
creator=self.music_creator,
|
||||
original_author=self.music_original_author,
|
||||
@@ -875,13 +880,13 @@ class SingleMusic(List[SingleTrack]):
|
||||
# yield _remain[1]
|
||||
|
||||
def get_tracked_notes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
self, start_time: float = 0, end_time: float = inf
|
||||
) -> Generator[Iterator[SingleNote], Any, None]:
|
||||
"""获取指定时间段的各个音轨的音符数据"""
|
||||
return (track.get_notes(start_time, end_time) for track in self.music_tracks)
|
||||
|
||||
def get_tracked_minenotes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
self, start_time: float = 0, end_time: float = inf
|
||||
) -> Generator[Iterator[MineNote], Any, None]:
|
||||
"""获取指定时间段的各个音轨的,供我的世界播放的音符数据类"""
|
||||
return (
|
||||
@@ -889,7 +894,7 @@ class SingleMusic(List[SingleTrack]):
|
||||
)
|
||||
|
||||
def get_notes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
self, start_time: float = 0, end_time: float = inf
|
||||
) -> Iterator[SingleNote]:
|
||||
"""获取指定时间段的所有音符数据,按照时间顺序"""
|
||||
if self.track_amount == 0:
|
||||
@@ -900,7 +905,7 @@ class SingleMusic(List[SingleTrack]):
|
||||
)
|
||||
|
||||
def get_minenotes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
self, start_time: float = 0, end_time: float = inf
|
||||
) -> Iterator[MineNote]:
|
||||
"""获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序"""
|
||||
if self.track_amount == 0:
|
||||
|
||||
Reference in New Issue
Block a user