mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-30 13:15:59 +00:00
Java版!MSQ流式解析初适配!
This commit is contained in:
@@ -6,7 +6,7 @@ Musicreater(音·创)
|
||||
A free open source library used for dealing with **Minecraft** digital musics.
|
||||
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
|
||||
The Licensor of Musicreater("this project") is Eilles Wan, bgArray.
|
||||
@@ -22,10 +22,10 @@ The Licensor of Musicreater("this project") is Eilles Wan, bgArray.
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__version__ = "2.2.2.2"
|
||||
__vername__ = "接口小兼容"
|
||||
__version__ = "2.2.3"
|
||||
__vername__ = "Java版欲适配、初步MSQ流式适配"
|
||||
__author__ = (
|
||||
("金羿", "Eilles Wan"),
|
||||
("金羿", "Eilles"),
|
||||
("诸葛亮与八卦阵", "bgArray"),
|
||||
("鱼旧梦", "ElapsingDreams"),
|
||||
("偷吃不是Touch", "Touch"),
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -58,6 +58,7 @@ class MidiDestroyedError(MSCTBaseException):
|
||||
# super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args)
|
||||
# 此错误在本版本内已经不再使用
|
||||
|
||||
|
||||
class CommandFormatError(RuntimeError):
|
||||
"""指令格式与目标格式不匹配而引起的错误"""
|
||||
|
||||
@@ -75,6 +76,10 @@ class CommandFormatError(RuntimeError):
|
||||
# 这TM是什么错误?
|
||||
# 我什么时候写的这玩意?
|
||||
# 我哪知道这说的是啥?
|
||||
# !!!
|
||||
# 我知道这是什么了 —— 金羿 2025 0401
|
||||
# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息
|
||||
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
||||
|
||||
|
||||
class NotDefineTempoError(MidiFormatException):
|
||||
@@ -109,7 +114,7 @@ class NoteOnOffMismatchError(MidiFormatException):
|
||||
super().__init__("音符不匹配", *args)
|
||||
|
||||
|
||||
class ZeroSpeedError(ZeroDivisionError):
|
||||
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
|
||||
"""以0作为播放速度的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
@@ -117,9 +122,26 @@ class ZeroSpeedError(ZeroDivisionError):
|
||||
super().__init__("播放速度为0", *args)
|
||||
|
||||
|
||||
class IllegalMinimumVolumeError(ValueError):
|
||||
class IllegalMinimumVolumeError(MSCTBaseException, ValueError):
|
||||
"""最小播放音量有误的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""最小播放音量错误"""
|
||||
super().__init__("最小播放音量超出范围", *args)
|
||||
super().__init__("最小播放音量超出范围", *args)
|
||||
|
||||
|
||||
class MusicSequenceDecodeError(MSCTBaseException):
|
||||
"""音乐序列解码错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音乐序列无法正确解码的错误"""
|
||||
super().__init__("解码音符序列文件时出现问题", *args)
|
||||
|
||||
|
||||
class MusicSequenceVerificationFailed(MusicSequenceDecodeError):
|
||||
"""音乐序列校验失败"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音符序列文件与其校验值不一致"""
|
||||
super().__init__("音符序列文件校验失败", *args)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -29,6 +29,359 @@ from .main import (
|
||||
from .types import Tuple, List, Dict, ChannelType
|
||||
|
||||
|
||||
class FutureMidiConvertJavaE(MidiConvert):
|
||||
|
||||
def form_java_progress_bar(
|
||||
self,
|
||||
max_score: int,
|
||||
scoreboard_name: str,
|
||||
progressbar_style: ProgressBarStyle = DEFAULT_PROGRESSBAR_STYLE,
|
||||
) -> List[MineCommand]:
|
||||
"""
|
||||
生成进度条
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_score: int
|
||||
最大的积分值
|
||||
scoreboard_name: str
|
||||
所使用的计分板名称
|
||||
progressbar_style: ProgressBarStyle
|
||||
此参数详见 ../docs/库的生成与功能文档.md#进度条自定义
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[MineCommand,]
|
||||
"""
|
||||
pgs_style = progressbar_style.base_style
|
||||
"""用于被替换的进度条原始样式"""
|
||||
|
||||
"""
|
||||
| 标识符 | 指定的可变量 |
|
||||
|---------|----------------|
|
||||
| `%%N` | 乐曲名(即传入的文件名)|
|
||||
| `%%s` | 当前计分板值 |
|
||||
| `%^s` | 计分板最大值 |
|
||||
| `%%t` | 当前播放时间 |
|
||||
| `%^t` | 曲目总时长 |
|
||||
| `%%%` | 当前进度比率 |
|
||||
| `_` | 用以表示进度条占位|
|
||||
"""
|
||||
perEach = max_score / pgs_style.count("_")
|
||||
"""每个进度条代表的分值"""
|
||||
|
||||
result: List[MineCommand] = []
|
||||
|
||||
if r"%^s" in pgs_style:
|
||||
pgs_style = pgs_style.replace(r"%^s", str(max_score))
|
||||
|
||||
if r"%^t" in pgs_style:
|
||||
pgs_style = pgs_style.replace(r"%^t", mctick2timestr(max_score))
|
||||
|
||||
sbn_pc = scoreboard_name[:2]
|
||||
if r"%%%" in pgs_style:
|
||||
result.append(
|
||||
MineCommand(
|
||||
'scoreboard objectives add {}PercT dummy "百分比计算"'.format(
|
||||
sbn_pc
|
||||
),
|
||||
annotation="新增临时百分比变量",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players set MaxScore {} {}".format(
|
||||
scoreboard_name, max_score
|
||||
),
|
||||
annotation="设定音乐最大延迟分数",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players set n100 {} 100".format(scoreboard_name),
|
||||
annotation="设置常量100",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[scores_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players operation @s {} = @s {}".format(
|
||||
sbn_pc + "PercT", scoreboard_name
|
||||
),
|
||||
annotation="赋值临时百分比",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players operation @s {} *= n100 {}".format(
|
||||
sbn_pc + "PercT", scoreboard_name
|
||||
),
|
||||
annotation="转换临时百分比之单位至%(扩大精度)",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players operation @s {} /= MaxScore {}".format(
|
||||
sbn_pc + "PercT", scoreboard_name
|
||||
),
|
||||
annotation="计算百分比",
|
||||
)
|
||||
)
|
||||
|
||||
if r"%%t" in pgs_style:
|
||||
result.append(
|
||||
MineCommand(
|
||||
'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format(
|
||||
sbn_pc
|
||||
),
|
||||
annotation="新增临时分变量",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(
|
||||
sbn_pc
|
||||
),
|
||||
annotation="新增临时秒变量",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players set n20 {} 20".format(scoreboard_name),
|
||||
annotation="设置常量20",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players set n60 {} 60".format(scoreboard_name),
|
||||
annotation="设置常量60",
|
||||
)
|
||||
)
|
||||
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players operation @s {} = @s {}".format(
|
||||
sbn_pc + "TMinT", scoreboard_name
|
||||
),
|
||||
annotation="赋值临时分",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players operation @s {} /= n20 {}".format(
|
||||
sbn_pc + "TMinT", scoreboard_name
|
||||
),
|
||||
annotation="转换临时分之单位为秒(缩减精度)",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players operation @s {} = @s {}".format(
|
||||
sbn_pc + "TSecT", sbn_pc + "TMinT"
|
||||
),
|
||||
annotation="赋值临时秒",
|
||||
)
|
||||
)
|
||||
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players operation @s {} /= n60 {}".format(
|
||||
sbn_pc + "TMinT", scoreboard_name
|
||||
),
|
||||
annotation="转换临时分之单位为分(缩减精度)",
|
||||
)
|
||||
)
|
||||
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_" + scoreboard_name + "_min=1]"
|
||||
)
|
||||
+ "scoreboard players operation @s {} %= n60 {}".format(
|
||||
sbn_pc + "TSecT", scoreboard_name
|
||||
),
|
||||
annotation="确定临时秒(框定精度区间)",
|
||||
)
|
||||
)
|
||||
|
||||
for i in range(pgs_style.count("_")):
|
||||
npg_stl = (
|
||||
pgs_style.replace("_", progressbar_style.played_style, i + 1)
|
||||
.replace("_", progressbar_style.to_play_style)
|
||||
.replace(r"%%N", self.music_name)
|
||||
.replace(
|
||||
r"%%s",
|
||||
'"},{"score":{"name":"*","objective":"'
|
||||
+ scoreboard_name
|
||||
+ '"}},{"text":"',
|
||||
)
|
||||
.replace(
|
||||
r"%%%",
|
||||
r'"},{"score":{"name":"*","objective":"'
|
||||
+ sbn_pc
|
||||
+ r'PercT"}},{"text":"%',
|
||||
)
|
||||
.replace(
|
||||
r"%%t",
|
||||
r'"},{"score":{"name":"*","objective":"{-}TMinT"}},{"text":":"},'
|
||||
r'{"score":{"name":"*","objective":"{-}TSecT"}},{"text":"'.replace(
|
||||
r"{-}", sbn_pc
|
||||
),
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
self.execute_cmd_head.format(
|
||||
f"@a[score_{scoreboard_name}_min={int(i * perEach)},score_{scoreboard_name}={math.ceil((i + 1) * perEach)}]"
|
||||
)
|
||||
+ r'titleraw @s actionbar {"rawtext":[{"text":"'
|
||||
+ npg_stl
|
||||
+ r'"}]}',
|
||||
annotation="进度条显示",
|
||||
)
|
||||
)
|
||||
|
||||
if r"%%%" in pgs_style:
|
||||
result.append(
|
||||
MineCommand(
|
||||
"scoreboard objectives remove {}PercT".format(sbn_pc),
|
||||
annotation="移除临时百分比变量",
|
||||
)
|
||||
)
|
||||
if r"%%t" in pgs_style:
|
||||
result.append(
|
||||
MineCommand(
|
||||
"scoreboard objectives remove {}TMinT".format(sbn_pc),
|
||||
annotation="移除临时分变量",
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
MineCommand(
|
||||
"scoreboard objectives remove {}TSecT".format(sbn_pc),
|
||||
annotation="移除临时秒变量",
|
||||
)
|
||||
)
|
||||
|
||||
self.progress_bar_command = result
|
||||
return result
|
||||
|
||||
def to_command_list_in_java_score(
|
||||
self,
|
||||
scoreboard_name: str = "mscplay",
|
||||
source_of_sound: str = "ambient",
|
||||
) -> Tuple[List[List[MineCommand]], int, int]:
|
||||
"""
|
||||
将midi转换为 Java 1.12.2 版我的世界命令列表
|
||||
|
||||
Parameters
|
||||
----------
|
||||
scoreboard_name: str
|
||||
我的世界的计分板名称
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple( list[list[MineCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
|
||||
"""
|
||||
|
||||
command_channels = []
|
||||
command_amount = 0
|
||||
max_score = 0
|
||||
|
||||
# 此处 我们把通道视为音轨
|
||||
for channel in self.channels.values():
|
||||
# 如果当前通道为空 则跳过
|
||||
if not channel:
|
||||
continue
|
||||
|
||||
this_channel = []
|
||||
|
||||
for note in channel:
|
||||
max_score = max(max_score, note.start_tick)
|
||||
|
||||
(
|
||||
mc_sound_ID,
|
||||
relative_coordinates,
|
||||
volume_percentage,
|
||||
mc_pitch,
|
||||
) = minenote_to_command_paramaters(
|
||||
note,
|
||||
pitch_deviation=self.music_deviation,
|
||||
)
|
||||
|
||||
this_channel.append(
|
||||
MineCommand(
|
||||
(
|
||||
self.execute_cmd_head.format(
|
||||
"@a[score_{0}_min={1},score_{0}={1}]".format(
|
||||
scoreboard_name, note.start_tick
|
||||
)
|
||||
.replace("(", r"{")
|
||||
.replace(")", r"}")
|
||||
)
|
||||
+ "playsound minecraft:block.{} {} @s ~{} ~{} ~{} {} {} {}".format(
|
||||
mc_sound_ID,
|
||||
source_of_sound,
|
||||
*relative_coordinates,
|
||||
volume_percentage,
|
||||
1.0 if note.percussive else mc_pitch,
|
||||
self.minimum_volume,
|
||||
)
|
||||
),
|
||||
annotation=(
|
||||
"在{}播放噪音{}".format(
|
||||
mctick2timestr(note.start_tick),
|
||||
mc_sound_ID,
|
||||
)
|
||||
if note.percussive
|
||||
else "在{}播放乐音{}".format(
|
||||
mctick2timestr(note.start_tick),
|
||||
"{}:{:.2f}".format(mc_sound_ID, mc_pitch),
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
command_amount += 1
|
||||
|
||||
if this_channel:
|
||||
self.music_command_list.extend(this_channel)
|
||||
command_channels.append(this_channel)
|
||||
|
||||
return command_channels, command_amount, max_score
|
||||
|
||||
|
||||
class FutureMidiConvertRSNB(MidiConvert):
|
||||
"""
|
||||
加入红石音乐适配
|
||||
@@ -217,7 +570,7 @@ class FutureMidiConvertM5(MidiConvert):
|
||||
if not track:
|
||||
continue
|
||||
|
||||
note_queue = empty_midi_channels(staff=[])
|
||||
note_queue = empty_midi_channels(default_staff=[])
|
||||
|
||||
for msg in track:
|
||||
if msg.time != 0:
|
||||
|
||||
@@ -23,7 +23,7 @@ Musicreater (音·创)
|
||||
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
|
||||
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 ../License.md
|
||||
Terms & Conditions: ../License.md
|
||||
|
||||
@@ -8,7 +8,7 @@ Musicreater (音·创)
|
||||
A free open source library used for dealing with **Minecraft** digital musics.
|
||||
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
|
||||
The Licensor of Musicreater("this project") is Eilles Wan, bgArray.
|
||||
@@ -30,10 +30,11 @@ The Licensor of Musicreater("this project") is Eilles Wan, bgArray.
|
||||
# 赶快呼叫 程序员!Let's Go! 直ぐに呼びましょプログラマ レッツゴー! Hurry to call the programmer! Let's Go!
|
||||
|
||||
|
||||
import math
|
||||
import os
|
||||
import math
|
||||
|
||||
import mido
|
||||
from xxhash import xxh3_64, xxh3_128
|
||||
|
||||
from .constants import *
|
||||
from .exceptions import *
|
||||
@@ -203,6 +204,7 @@ class MusicSequence:
|
||||
) = cls.to_music_note_channels(
|
||||
midi=mido_file,
|
||||
speed=speed_multiplier,
|
||||
default_program_value=-1, # TODO 默认音色可调
|
||||
pitched_note_rtable=pitched_note_referance_table,
|
||||
percussion_note_rtable=percussion_note_referance_table,
|
||||
default_tempo_value=default_tempo,
|
||||
@@ -226,6 +228,7 @@ class MusicSequence:
|
||||
def load_decode(
|
||||
cls,
|
||||
bytes_buffer_in: bytes,
|
||||
verify: bool = True,
|
||||
):
|
||||
"""从字节码导入音乐序列"""
|
||||
|
||||
@@ -238,8 +241,16 @@ class MusicSequence:
|
||||
music_name_ = bytes_buffer_in[8 : (stt_index := 8 + (group_1 >> 10))].decode(
|
||||
"GB18030"
|
||||
)
|
||||
channels_: MineNoteChannelType = empty_midi_channels(staff=[])
|
||||
channels_: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
||||
total_note_count = 0
|
||||
if verify:
|
||||
_header_index = stt_index
|
||||
_total_verify_code = 0
|
||||
|
||||
for channel_index in channels_.keys():
|
||||
channel_note_count = 0
|
||||
_channel_start_index = stt_index
|
||||
|
||||
for i in range(
|
||||
int.from_bytes(
|
||||
bytes_buffer_in[stt_index : (stt_index := stt_index + 4)], "big"
|
||||
@@ -258,10 +269,70 @@ class MusicSequence:
|
||||
is_high_time_precision=high_quantity,
|
||||
)
|
||||
)
|
||||
channel_note_count += 1
|
||||
stt_index = end_index
|
||||
except:
|
||||
except Exception as _err:
|
||||
print(channels_)
|
||||
raise
|
||||
raise MusicSequenceDecodeError(_err)
|
||||
if verify:
|
||||
if (
|
||||
_count_verify := xxh3_64(
|
||||
channel_note_count.to_bytes(4, "big", signed=False),
|
||||
seed=3,
|
||||
)
|
||||
).digest() != (
|
||||
_original_code := bytes_buffer_in[stt_index : stt_index + 8]
|
||||
):
|
||||
raise MusicSequenceVerificationFailed(
|
||||
"通道 {} 音符数量校验失败:{} -> `{}`;原始为 `{}`".format(
|
||||
channel_index,
|
||||
channel_note_count,
|
||||
_count_verify.digest(),
|
||||
_original_code,
|
||||
)
|
||||
)
|
||||
if (
|
||||
_channel_verify := xxh3_64(
|
||||
bytes_buffer_in[_channel_start_index:stt_index],
|
||||
seed=channel_note_count,
|
||||
)
|
||||
).digest() != (
|
||||
_original_code := bytes_buffer_in[stt_index + 8 : stt_index + 16]
|
||||
):
|
||||
raise MusicSequenceVerificationFailed(
|
||||
"通道 {} 音符数据校验失败:`{}`;原始为 `{}`".format(
|
||||
channel_index,
|
||||
_channel_verify.digest(),
|
||||
_original_code,
|
||||
)
|
||||
)
|
||||
_total_verify_code ^= (
|
||||
_count_verify.intdigest() ^ _channel_verify.intdigest()
|
||||
)
|
||||
total_note_count += channel_note_count
|
||||
stt_index += 16
|
||||
|
||||
if verify:
|
||||
if (
|
||||
_total_verify_res := xxh3_128(
|
||||
_total_verify := (
|
||||
xxh3_64(
|
||||
bytes_buffer_in[0:_header_index],
|
||||
seed=total_note_count,
|
||||
).intdigest()
|
||||
^ _total_verify_code
|
||||
).to_bytes(8, "big"),
|
||||
seed=total_note_count,
|
||||
).digest()
|
||||
) != (_original_code := bytes_buffer_in[stt_index:]):
|
||||
raise MusicSequenceVerificationFailed(
|
||||
"全曲最终校验失败。全曲音符数:{},全曲校验码异或和:`{}` -> `{}`;原始为 `{}`".format(
|
||||
total_note_count,
|
||||
_total_verify,
|
||||
_total_verify_res,
|
||||
_original_code,
|
||||
)
|
||||
)
|
||||
|
||||
return cls(
|
||||
name_of_music=music_name_,
|
||||
@@ -280,6 +351,7 @@ class MusicSequence:
|
||||
) -> bytes:
|
||||
"""将音乐序列转为二进制字节码"""
|
||||
|
||||
# (已废弃)
|
||||
# 第一版的码头: MSQ# 字串编码: UTF-8
|
||||
# 第一版格式
|
||||
# 音乐名称长度 6 位 支持到 63
|
||||
@@ -306,8 +378,11 @@ class MusicSequence:
|
||||
# for note_ in note_list:
|
||||
# bytes_buffer += note_.encode()
|
||||
|
||||
# (已废弃)
|
||||
# 第二版的码头: MSQ@ 字串编码: GB18030
|
||||
|
||||
# 第三版的码头: MSQ! 字串编码: GB18030 大端字节序
|
||||
|
||||
# 音乐名称长度 6 位 支持到 63
|
||||
# 最小音量 minimum_volume 10 位 最大支持 1023 即三位小数
|
||||
# 共 16 位 合 2 字节
|
||||
@@ -316,33 +391,73 @@ class MusicSequence:
|
||||
# 总音调偏移 music_deviation 15 位 最大支持 -16383 ~ 16383 即 三位小数
|
||||
# 共 16 位 合 2 字节
|
||||
# +++
|
||||
# 音乐名称 music_name 长度最多63 支持到 31 个中文字符 或 63 个西文字符
|
||||
# 音乐名称 music_name 长度最多 63 支持到 31 个中文字符 或 63 个西文字符
|
||||
|
||||
bytes_buffer = (
|
||||
b"MSQ@"
|
||||
b"MSQ!"
|
||||
+ (
|
||||
(len(r := self.music_name.encode("GB18030")) << 10) # 音乐名称长度
|
||||
+ round(self.minimum_volume * 1000) # 最小音量
|
||||
(len(r := self.music_name.encode("GB18030")) << 10) # 音乐名称长度
|
||||
+ round(self.minimum_volume * 1000) # 最小音量
|
||||
).to_bytes(2, "big")
|
||||
+ (
|
||||
(
|
||||
(
|
||||
(high_time_precision << 1) # 是否启用“高精度”音符时间控制
|
||||
+ (1 if (k := round(self.music_deviation * 1000)) < 0 else 0) # 总音调偏移的正负位
|
||||
+ (
|
||||
1 if (k := round(self.music_deviation * 1000)) < 0 else 0
|
||||
) # 总音调偏移的正负位
|
||||
)
|
||||
<< 14
|
||||
)
|
||||
+ abs(k) # 总音调偏移
|
||||
+ abs(k) # 总音调偏移
|
||||
).to_bytes(2, "big", signed=False)
|
||||
+ r
|
||||
)
|
||||
|
||||
# 此上是音符序列的元信息,接下来是多通道的音符序列
|
||||
|
||||
# 每个通道的开头是 32 位的 序列长度 共 4 字节
|
||||
# 接下来根据这个序列的长度来读取音符数据
|
||||
|
||||
# 若启用“高精度”,则每个音符皆添加一个字节,用于存储音符时间控制精度偏移
|
||||
# 此值每增加 1,则音符向后播放时长增加 1/1250 秒
|
||||
# 高精度功能在 MineNote 类实现
|
||||
|
||||
# (第三版新增)每个通道结尾包含一个 128 位的 XXHASH 校验值,用以标识该通道结束
|
||||
# 在这 128 位里,前 64 位是该通道音符数的 XXHASH64 校验值,以 3 作为种子值
|
||||
# 后 64 位是整个通道全部字节串的 XXHASH64 校验值(包括通道开头的音符数),以 该通道音符数 作为种子值
|
||||
_final_hash_codec = xxh3_64(
|
||||
bytes_buffer, seed=self.total_note_count
|
||||
).intdigest()
|
||||
|
||||
for channel_index, note_list in self.channels.items():
|
||||
bytes_buffer += len(note_list).to_bytes(4, "big")
|
||||
channel_buffer = len_buffer = len(note_list).to_bytes(
|
||||
4, "big", signed=False
|
||||
)
|
||||
for note_ in note_list:
|
||||
bytes_buffer += note_.encode(is_high_time_precision=high_time_precision)
|
||||
channel_buffer += note_.encode(
|
||||
is_high_time_precision=high_time_precision
|
||||
)
|
||||
_now_hash_codec_spliter = xxh3_64(len_buffer, seed=3)
|
||||
_now_hash_codec_verifier = xxh3_64(
|
||||
channel_buffer, seed=int.from_bytes(len_buffer, "big", signed=False)
|
||||
)
|
||||
|
||||
bytes_buffer += channel_buffer
|
||||
bytes_buffer += (
|
||||
_now_hash_codec_spliter.digest() + _now_hash_codec_verifier.digest()
|
||||
)
|
||||
|
||||
_final_hash_codec ^= (
|
||||
_now_hash_codec_spliter.intdigest()
|
||||
^ _now_hash_codec_verifier.intdigest()
|
||||
)
|
||||
|
||||
# 在所有音符通道表示完毕之后,由一个 128 位的 XXHASH 校验值,用以标识文件结束并校验
|
||||
# 该 128 位的校验值是对于前述所有校验值的异或所得值之 XXHASH128 校验值,以 全曲音符总数 作为种子值
|
||||
bytes_buffer += xxh3_128(
|
||||
_final_hash_codec.to_bytes(8, "big"), seed=self.total_note_count
|
||||
).digest()
|
||||
|
||||
return bytes_buffer
|
||||
|
||||
@@ -378,6 +493,7 @@ class MusicSequence:
|
||||
midi: mido.MidiFile,
|
||||
ignore_mismatch_error: bool = True,
|
||||
speed: float = 1.0,
|
||||
default_program_value: int = -1,
|
||||
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||
pitched_note_rtable: MidiInstrumentTableType = MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||
percussion_note_rtable: MidiInstrumentTableType = MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||
@@ -392,8 +508,10 @@ class MusicSequence:
|
||||
需要处理的midi对象
|
||||
speed: float
|
||||
音乐播放速度倍数
|
||||
default_program_value: int
|
||||
默认的 MIDI 乐器值
|
||||
default_tempo_value: int
|
||||
默认的MIDI TEMPO值
|
||||
默认的 MIDI TEMPO 值
|
||||
pitched_note_rtable: Dict[int, Tuple[str, int]]
|
||||
乐音乐器Midi-MC对照表
|
||||
percussion_note_rtable: Dict[int, Tuple[str, int]]
|
||||
@@ -411,8 +529,10 @@ class MusicSequence:
|
||||
raise ZeroSpeedError("播放速度为 0 ,其需要(0,1]范围内的实数。")
|
||||
|
||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||
midi_channels: MineNoteChannelType = empty_midi_channels(staff=[])
|
||||
channel_program: Dict[int, int] = empty_midi_channels(staff=-1)
|
||||
midi_channels: MineNoteChannelType = empty_midi_channels(default_staff=[])
|
||||
channel_program: Dict[int, int] = empty_midi_channels(
|
||||
default_staff=default_program_value
|
||||
)
|
||||
tempo = default_tempo_value
|
||||
note_count = 0
|
||||
note_count_per_instrument: Dict[str, int] = {}
|
||||
@@ -426,7 +546,7 @@ class MusicSequence:
|
||||
int,
|
||||
]
|
||||
],
|
||||
] = empty_midi_channels(staff=[])
|
||||
] = empty_midi_channels(default_staff=[])
|
||||
note_queue_B: Dict[
|
||||
int,
|
||||
List[
|
||||
@@ -435,7 +555,7 @@ class MusicSequence:
|
||||
int,
|
||||
]
|
||||
],
|
||||
] = empty_midi_channels(staff=[])
|
||||
] = empty_midi_channels(default_staff=[])
|
||||
|
||||
# 直接使用mido.midifiles.tracks.merge_tracks转为单轨
|
||||
# 采用的时遍历信息思路
|
||||
@@ -957,7 +1077,9 @@ class MidiConvert(MusicSequence):
|
||||
self.progress_bar_command = result
|
||||
return result
|
||||
|
||||
def redefine_execute_format(self, is_old_exe_cmd_using: bool = False):
|
||||
def redefine_execute_format(
|
||||
self, is_old_exe_cmd_using: bool = False
|
||||
) -> "MidiConvert":
|
||||
"""
|
||||
根据是否使用旧版执行命令格式,重新定义执行命令的起始格式。
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
存放非音·创本体的附加功能件
|
||||
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
用以生成附加包的附加功能
|
||||
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
用以生成BDX结构文件的附加功能
|
||||
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
用以生成单个mcstructure文件的附加功能
|
||||
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
用以生成Schematic结构的附加功能
|
||||
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
用以启动WebSocket服务器播放的附加功能
|
||||
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2024 EillesWan & bgArray
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -38,7 +38,9 @@ from .types import (
|
||||
)
|
||||
|
||||
|
||||
def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]:
|
||||
def empty_midi_channels(
|
||||
channel_count: int = 17, default_staff: Any = {}
|
||||
) -> Dict[int, Any]:
|
||||
"""
|
||||
空MIDI通道字典
|
||||
"""
|
||||
@@ -46,7 +48,11 @@ def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, A
|
||||
return dict(
|
||||
(
|
||||
i,
|
||||
(staff.copy() if isinstance(staff, (dict, list)) else staff),
|
||||
(
|
||||
default_staff.copy()
|
||||
if isinstance(default_staff, (dict, list))
|
||||
else default_staff
|
||||
),
|
||||
) # 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
||||
for i in range(channel_count)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user