mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2025-06-03 02:45:24 +00:00
Java版!MSQ流式解析初适配!
This commit is contained in:
parent
2df8d6a270
commit
889f8f9641
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,9 +1,10 @@
|
||||
# sth. can't open
|
||||
/msctPkgver/secrets/*.py
|
||||
/msctPkgver/secrets/*.c
|
||||
|
||||
/fool/
|
||||
|
||||
# mystuff
|
||||
/*.zip
|
||||
/.vscode
|
||||
/*.mid
|
||||
/*.midi
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -91,6 +91,7 @@
|
||||
- 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题。
|
||||
- <table><tr><td>感谢 **油炸**<QQ2836146704> 激励我们不断开发新的内容。</td><td><img height="50" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg"></td></tr></table>
|
||||
- 感谢 **雨**\<QQ237667809\> 反馈在新版本的指令格式下,计分板播放器的附加包无法播放的问题。
|
||||
- 感谢 **梦幻duang**\<QQ13753593\> 为我们提供 Java 1.12.2 版本命令格式参考。
|
||||
|
||||
> 感谢广大群友为此库提供的测试和建议等
|
||||
>
|
||||
|
3
docs/FSQ文件格式.md
Normal file
3
docs/FSQ文件格式.md
Normal file
@ -0,0 +1,3 @@
|
||||
# FSQ 文件格式
|
||||
|
||||
还在设计
|
@ -2,18 +2,25 @@
|
||||
|
||||
MSQ 文件是 音·创 存储音符序列的一种格式,取自 MusicSeQuence。
|
||||
|
||||
## MSQ 第二版
|
||||
现在 音·创 及其上游软件使用的是在 第二版 的基础上增设验证功能的 MSQ 第三版。
|
||||
|
||||
第二版的码头是 MSQ@ ,这一版中,所有的**字符串**以 _**GB18030**_ 编码进行编解码,**数值**以**_大端序_**存储。
|
||||
## MSQ 第三版
|
||||
|
||||
码头是文件前四个字节的内容,这一部分内容是可读的 ASCII 字串。因此,第二版的文件前四个字节的内容必为 MSQ@。
|
||||
第二版的码头是 `MSQ@` ,这一版中,所有的**字符串**以 _**GB18030**_ 编码进行编解码,**数值**以**_大端序_**存储。
|
||||
|
||||
MSQ 第三版的码头是 `MSQ!`。
|
||||
|
||||
码头是文件前四个字节的内容,这一部分内容是可读的 ASCII 字串。因此,第三版的文件前四个字节的内容必为 `MSQ!`。
|
||||
|
||||
取 MSQ@ 是因为美式键盘上 @ 是 Shift+2 键 按下取得的,故代表 MSQ 第二版。
|
||||
|
||||
你猜为什么第三版是 `MSQ!`。
|
||||
|
||||
### 元信息
|
||||
|
||||
| 信息名称 | 西文代号 | 位长(多少个 0 或 1) | 支持说明 |
|
||||
| ------------------------------ | -------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **码头** | _无_ | 32 位 | 值为 `MSQ!` |
|
||||
| **音乐名称长度** | music_name_length | 6 位 | 支持数值 0~63 |
|
||||
| **最小音量** | minimum_volume | 10 位 | 支持数值 0~1023,注意,这里每个 1 代表最小音量的 0.001 个单位,即取值是此处表示数字的千分倍 |
|
||||
| **是否启用高精度音符时间控制** | enable_high_precision_time | 1 位 | 1 是启用,反之同理 |
|
||||
@ -39,4 +46,58 @@ MSQ 文件是 音·创 存储音符序列的一种格式,取自 MusicSeQuence
|
||||
| **是否启用声像位移** | is_displacement_included | 1 位 | 1 是启用,反之同理 |
|
||||
| **时间精度提升值**(非必含) | high_time_precision | 8 位 | 支持数值 0~255,若在 元信息 中启用**高精度音符时间控制**,则此值启用,代表音符时间控制精度偏移,此值每增加 1,则音符开始时刻向后增加 1/1250 秒 |
|
||||
| **乐器名称** | sound_name | 依据先前定义 | 最多可支持 31 个中文字符 或 63 个西文字符,其长度取决于先前获知的 “乐器名称长度” 的定义 |
|
||||
| **声像位移**(非必含) | position_displacement | 共三个值,每个值 16 位 共 48 位 | 若前述**是否启用声像位移**已启用,则此值启用;三个值分别代表 x、y、z 轴上的便宜,每个值支持数值 0~65535,注意,这里每个 1 代表最小音量的 0.001 个单位,即取值是此处表示数字的千分倍 |
|
||||
| **声像位移**(非必含) | position_displacement | 共三个值,每个值 16 位 共 48 位 | 若前述**是否启用声像位移**已启用,则此值启用;三个值分别代表 x、y、z 轴上的偏移,每个值支持数值 0~65535,注意,这里每个 1 代表最小音量的 0.001 个单位,即取值是此处表示数字的千分倍 |
|
||||
|
||||
### 序列验证
|
||||
|
||||
_第三版新增_
|
||||
|
||||
在每个音符序列结尾包含一个 128 位的校验值,用以标识该序列结束的同时,验证该序列的完整性。
|
||||
在这 128 位里,前 64 位是该通道音符数的 XXHASH64 校验值,以 3 作为种子值。
|
||||
后 64 位是整个通道全部字节串的 XXHASH64 校验值(包括通道开头的音符数),以 该通道音符数 作为种子值。
|
||||
|
||||
### 文件验证
|
||||
|
||||
_第三版新增_
|
||||
|
||||
在所有有效数据之后,包含一个 128 位的校验值,用以标识整个文件结束的同时,验证整个文件的完整性。
|
||||
|
||||
该 128 位的校验值是 包括码头在内的元信息的 XXHASH64 校验值(种子值是全曲音符数) 对于前述所有校验值彼此异或的异或 所得值之 XXHASH128 校验值,以 全曲音符总数 作为种子值。
|
||||
|
||||
请注意,是前述每个 XXHASH64 校验值的异或(每次取 XXHASH64 都计一遍),也就并非是每个序列结尾,那个已经合并了的 128 位校验值再彼此异或。对于这个异或值,再取其种子是 全曲音符数 的 XXHASH128 校验字节码。
|
||||
|
||||
听起来很复杂?我来举个例子。以下是该算法的伪代码。我们设:
|
||||
|
||||
- `meta_info` : `bytes` 为 元信息字节串
|
||||
- `note_seq_1` : `bytes` 为 第一个音符序列的编码字节串
|
||||
- `note_seq_2` : `bytes` 为 第二个音符序列的编码字节串
|
||||
- `XXH64(bytes, seed)` : `bytes` 为 XXHASH64 校验函数
|
||||
- `XXH128(bytes, seed)` : `bytes` 为 XXHASH128 校验函数
|
||||
- `XOR(bytesLike, bytesLike)` : `bytes` 为 异或 函数
|
||||
- `note_count` : `int` 为 全曲音符数
|
||||
- `seq_1_note_count` : `int` 为 第一个音符序列的音符数
|
||||
- `seq_2_note_count` : `int` 为 第二个音符序列的音符数
|
||||
|
||||
为了简化,我们假设只有两个序列,实际上每个通道都是一个序列(最多 16 个序列)
|
||||
|
||||
那么,一个完整的 MSQ 文件应当如下排列其字节串:
|
||||
|
||||
```assembly
|
||||
ADD meta_info
|
||||
ADD note_seq_1
|
||||
ADD XXH64(seq_1_note_count, 3)
|
||||
ADD XXH64(note_seq_1, seq_1_note_count)
|
||||
ADD note_seq_2
|
||||
ADD XXH64(seq_2_note_count, 3)
|
||||
ADD XXH64(note_seq_2, seq_2_note_count)
|
||||
ADD XXH128(
|
||||
XOR(
|
||||
XXH64(meta_info, note_count),
|
||||
XOR(
|
||||
XXH64(seq_1_note_count, 3),
|
||||
XXH64(note_seq_1, seq_1_note_count)
|
||||
),
|
||||
),
|
||||
note_count
|
||||
)
|
||||
```
|
||||
|
@ -13,7 +13,7 @@
|
||||
**_使用时请遵循协议规定_**
|
||||
|
||||
- 版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
- Copyright © 2024 EillesWan & bgArray
|
||||
- Copyright © 2025 Eilles & bgArray
|
||||
|
||||
* 开源相关声明请见 仓库根目录下的 License.md
|
||||
* Terms & Conditions: License.md in the root directory
|
||||
|
@ -10,7 +10,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
|
||||
|
14
example_msq_opera.py
Normal file
14
example_msq_opera.py
Normal file
@ -0,0 +1,14 @@
|
||||
import Musicreater
|
||||
|
||||
msc_seq = Musicreater.MusicSequence.from_mido(
|
||||
Musicreater.mido.MidiFile("./resources/测试片段.mid",),
|
||||
"TEST-测试片段",
|
||||
)
|
||||
|
||||
with open("test.msq","wb") as f:
|
||||
f.write(msq_bytes := msc_seq.encode_dump())
|
||||
|
||||
with open("test.msq","rb") as f:
|
||||
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read())
|
||||
|
||||
print(msc_seq_r)
|
141
let_future_java.py
Normal file
141
let_future_java.py
Normal file
@ -0,0 +1,141 @@
|
||||
"""
|
||||
版权所有 © 2024 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
import Musicreater.experiment
|
||||
from Musicreater.plugin.archive import compress_zipfile
|
||||
|
||||
|
||||
def to_zip_pack_in_score(
|
||||
midi_cvt: Musicreater.experiment.FutureMidiConvertJavaE,
|
||||
dist_path: str,
|
||||
progressbar_style: Optional[Musicreater.experiment.ProgressBarStyle],
|
||||
scoreboard_name: str = "mscplay",
|
||||
sound_source: str = "ambient",
|
||||
auto_reset: bool = False,
|
||||
) -> Tuple[int, int]:
|
||||
"""
|
||||
将midi以计分播放器形式转换为我的世界函数附加包
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
dist_path: str
|
||||
转换结果输出的目标路径
|
||||
progressbar_style: ProgressBarStyle 对象
|
||||
进度条对象
|
||||
scoreboard_name: str
|
||||
我的世界的计分板名称
|
||||
auto_reset: bool
|
||||
是否自动重置计分板
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[int指令数量, int音乐总延迟]
|
||||
"""
|
||||
|
||||
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_java_score(
|
||||
scoreboard_name=scoreboard_name,
|
||||
source_of_sound=sound_source,
|
||||
)
|
||||
|
||||
# 当文件f夹{self.outputPath}/temp/mscplyfuncs存在时清空其下所有项目,然后创建
|
||||
if os.path.exists(f"{dist_path}/temp/mscplyfuncs/"):
|
||||
shutil.rmtree(f"{dist_path}/temp/mscplyfuncs/")
|
||||
os.makedirs(f"{dist_path}/temp/mscplyfuncs/mscplay")
|
||||
|
||||
# 写入stop.mcfunction
|
||||
with open(
|
||||
f"{dist_path}/temp/mscplyfuncs/stop.mcfunction", "w", encoding="utf-8"
|
||||
) as f:
|
||||
f.write("scoreboard players reset @a {}".format(scoreboard_name))
|
||||
|
||||
# 将命令列表写入文件
|
||||
index_file = open(
|
||||
f"{dist_path}/temp/mscplyfuncs/index.mcfunction", "w", encoding="utf-8"
|
||||
)
|
||||
for i in range(len(cmdlist)):
|
||||
index_file.write(f"function mscplyfuncs:mscplay/track{i + 1}\n")
|
||||
with open(
|
||||
f"{dist_path}/temp/mscplyfuncs/mscplay/track{i + 1}.mcfunction",
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]]))
|
||||
index_file.writelines(
|
||||
(
|
||||
"scoreboard players add @a[score_{0}_min=1] {0} 1\n".format(
|
||||
scoreboard_name
|
||||
),
|
||||
(
|
||||
"scoreboard players reset @a[score_{0}_min={1}] {0}\n".format(
|
||||
scoreboard_name, maxscore + 20
|
||||
)
|
||||
if auto_reset
|
||||
else ""
|
||||
),
|
||||
f"function mscplyfuncs:mscplay/progressShow\n" if progressbar_style else "",
|
||||
)
|
||||
)
|
||||
|
||||
if progressbar_style:
|
||||
with open(
|
||||
f"{dist_path}/temp/mscplyfuncs/mscplay/progressShow.mcfunction",
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
f.writelines(
|
||||
"\n".join(
|
||||
[
|
||||
single_cmd.cmd
|
||||
for single_cmd in midi_cvt.form_java_progress_bar(
|
||||
maxscore, scoreboard_name, progressbar_style
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
index_file.close()
|
||||
|
||||
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.zip"):
|
||||
os.remove(f"{dist_path}/{midi_cvt.music_name}.zip")
|
||||
compress_zipfile(
|
||||
f"{dist_path}/temp/",
|
||||
f"{dist_path}/{midi_cvt.music_name}[JEscore].zip",
|
||||
)
|
||||
|
||||
shutil.rmtree(f"{dist_path}/temp/")
|
||||
|
||||
return maxlen, maxscore
|
||||
|
||||
|
||||
print(
|
||||
to_zip_pack_in_score(
|
||||
Musicreater.experiment.FutureMidiConvertJavaE.from_midi_file(
|
||||
input("midi路径:"),
|
||||
play_speed=float(input("播放速度:")),
|
||||
old_exe_format=True,
|
||||
),
|
||||
input("输出路径:"),
|
||||
Musicreater.experiment.ProgressBarStyle(),
|
||||
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||
scoreboard_name=input("计分板名称:"),
|
||||
sound_source=input("发音源:"),
|
||||
auto_reset=True,
|
||||
)
|
||||
)
|
@ -1 +1,2 @@
|
||||
mido>=1.3
|
||||
xxhash>=3
|
Loading…
x
Reference in New Issue
Block a user