mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2024-11-14 11:27:42 +08:00
更高效的算法管理与兼容性和代码格式更新,详见样例代码;同时新增实验算法,在其中尝试下次更新的内容
This commit is contained in:
parent
36f8db722b
commit
f73c1be944
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@
|
|||||||
/llc_cli.py
|
/llc_cli.py
|
||||||
/utils
|
/utils
|
||||||
test.py
|
test.py
|
||||||
|
RES.txt
|
||||||
|
|
||||||
# Byte-compiled / optimized
|
# Byte-compiled / optimized
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
@ -17,8 +17,16 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__version__ = "1.1.1"
|
__version__ = "1.2.0"
|
||||||
__all__ = []
|
__vername__ = "更高效的算法管理"
|
||||||
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon"))
|
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon"))
|
||||||
|
__all__ = [
|
||||||
|
# 主要类
|
||||||
|
"MidiConvert",
|
||||||
|
# 附加类
|
||||||
|
# "SingleNote",
|
||||||
|
"SingleCommand",
|
||||||
|
# "TimeStamp", 未来功能
|
||||||
|
]
|
||||||
|
|
||||||
from .main import *
|
from .main import *
|
||||||
|
@ -17,8 +17,6 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MSCTBaseException(Exception):
|
class MSCTBaseException(Exception):
|
||||||
"""音·创库版本的所有错误均继承于此"""
|
"""音·创库版本的所有错误均继承于此"""
|
||||||
|
|
||||||
@ -52,6 +50,14 @@ class MidiDestroyedError(MSCTBaseException):
|
|||||||
super().__init__("MIDI文件损坏:无法读取MIDI文件", *args)
|
super().__init__("MIDI文件损坏:无法读取MIDI文件", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class MidiUnboundError(MSCTBaseException):
|
||||||
|
"""未定义Midi对象"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""未绑定Midi对象"""
|
||||||
|
super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args)
|
||||||
|
|
||||||
|
|
||||||
class CommandFormatError(RuntimeError):
|
class CommandFormatError(RuntimeError):
|
||||||
"""指令格式与目标格式不匹配而引起的错误"""
|
"""指令格式与目标格式不匹配而引起的错误"""
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
新版本功能以及即将启用的函数
|
新版本功能以及即将启用的函数
|
||||||
"""
|
"""
|
||||||
@ -17,98 +16,100 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from typing import Dict, List, Tuple, Union
|
||||||
|
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .main import MidiConvert, mido
|
from .main import MidiConvert
|
||||||
from .subclass import *
|
from .subclass import *
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
||||||
|
|
||||||
# 简单的单音填充
|
class FutureMidiConvertM4(MidiConvert):
|
||||||
def _toCmdList_m4(
|
|
||||||
self: MidiConvert,
|
|
||||||
scoreboard_name: str = "mscplay",
|
|
||||||
MaxVolume: float = 1.0,
|
|
||||||
speed: float = 1.0,
|
|
||||||
) -> list:
|
|
||||||
"""
|
"""
|
||||||
使用金羿的转换思路,将midi转换为我的世界命令列表,并使用完全填充算法优化音感
|
加入插值算法优化音感
|
||||||
:param scoreboard_name: 我的世界的计分板名称
|
: 经测试,生成效果已经达到,感觉良好
|
||||||
:param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
|
||||||
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
|
||||||
:return: tuple(命令列表, 命令个数, 计分板最大值)
|
|
||||||
"""
|
"""
|
||||||
# TODO: 这里的时间转换不知道有没有问题
|
|
||||||
|
|
||||||
if speed == 0:
|
# 临时用的插值计算函数
|
||||||
if self.debug_mode:
|
@staticmethod
|
||||||
raise ZeroSpeedError("播放速度仅可为正实数")
|
def _linear_note(
|
||||||
speed = 1
|
_note: SingleNote,
|
||||||
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
|
_apply_time_division: int = 100,
|
||||||
|
) -> List[Tuple[int, int, int, int, float],]:
|
||||||
|
"""传入音符数据,返回以半秒为分割的插值列表
|
||||||
|
:param _note: SingleNote 音符
|
||||||
|
:return list[tuple(int开始时间(毫秒), int乐器, int音符, int力度(内置), float音量(播放)),]"""
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
totalCount = int(_note.duration / _apply_time_division)
|
||||||
channels = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
|
if totalCount == 0:
|
||||||
|
return [
|
||||||
|
(_note.start_time, _note.inst, _note.pitch, _note.velocity, 1),
|
||||||
|
]
|
||||||
|
# print(totalCount)
|
||||||
|
|
||||||
# 我们来用通道统计音乐信息
|
result: List[
|
||||||
for i, track in enumerate(self.midi.tracks):
|
Tuple[int, int, int, int, float],
|
||||||
microseconds = 0
|
] = []
|
||||||
|
|
||||||
for msg in track:
|
for _i in range(totalCount):
|
||||||
if msg.time != 0:
|
result.append(
|
||||||
try:
|
(
|
||||||
microseconds += msg.time * tempo / self.midi.ticks_per_beat
|
_note.start_time + _i * _apply_time_division,
|
||||||
except NameError:
|
_note.instrument,
|
||||||
raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
|
_note.pitch,
|
||||||
|
_note.velocity,
|
||||||
if msg.is_meta:
|
((totalCount - _i) / totalCount),
|
||||||
if msg.type == "set_tempo":
|
)
|
||||||
tempo = msg.tempo
|
|
||||||
else:
|
|
||||||
if self.debug_mode:
|
|
||||||
try:
|
|
||||||
if msg.channel > 15:
|
|
||||||
raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if msg.type == "program_change":
|
|
||||||
channels[msg.channel].append(("PgmC", msg.program, microseconds))
|
|
||||||
|
|
||||||
elif msg.type == "note_on" and msg.velocity != 0:
|
|
||||||
channels[msg.channel].append(
|
|
||||||
("NoteS", msg.note, msg.velocity, microseconds)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
elif (msg.type == "note_on" and msg.velocity == 0) or (
|
return result
|
||||||
msg.type == "note_off"
|
|
||||||
):
|
|
||||||
channels[msg.channel].append(("NoteE", msg.note, microseconds))
|
|
||||||
|
|
||||||
"""整合后的音乐通道格式
|
# 简单的单音填充
|
||||||
每个通道包括若干消息元素其中逃不过这三种:
|
def to_command_list_in_score(
|
||||||
|
self,
|
||||||
|
scoreboard_name: str = "mscplay",
|
||||||
|
max_volume: float = 1.0,
|
||||||
|
speed: float = 1.0,
|
||||||
|
) -> Tuple[List[List[SingleCommand]], int, int]:
|
||||||
|
"""
|
||||||
|
使用金羿的转换思路,使用完全填充算法优化音感后,将midi转换为我的世界命令列表
|
||||||
|
|
||||||
1 切换乐器消息
|
Parameters
|
||||||
|
----------
|
||||||
|
scoreboard_name: str
|
||||||
|
我的世界的计分板名称
|
||||||
|
max_volume: float
|
||||||
|
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放
|
||||||
|
speed: float
|
||||||
|
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||||
|
|
||||||
("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
|
Returns
|
||||||
|
-------
|
||||||
|
tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
|
||||||
|
"""
|
||||||
|
|
||||||
2 音符开始消息
|
if speed == 0:
|
||||||
|
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||||
|
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
||||||
|
|
||||||
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
|
self.to_music_channels()
|
||||||
|
|
||||||
3 音符结束消息
|
note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[])
|
||||||
|
InstID = -1
|
||||||
("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
|
|
||||||
|
|
||||||
note_channels = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
|
|
||||||
|
|
||||||
# 此处 我们把通道视为音轨
|
# 此处 我们把通道视为音轨
|
||||||
for i in range(len(channels)):
|
for i in self.channels.keys():
|
||||||
# 如果当前通道为空 则跳过
|
# 如果当前通道为空 则跳过
|
||||||
|
if not self.channels[i]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# nowChannel = []
|
||||||
|
for track_no, track in self.channels[i].items():
|
||||||
|
|
||||||
noteMsgs = []
|
noteMsgs = []
|
||||||
MsgIndex = []
|
MsgIndex = []
|
||||||
|
|
||||||
for msg in channels[i]:
|
for msg in track:
|
||||||
if msg[0] == "PgmC":
|
if msg[0] == "PgmC":
|
||||||
InstID = msg[1]
|
InstID = msg[1]
|
||||||
|
|
||||||
@ -125,82 +126,368 @@ def _toCmdList_m4(
|
|||||||
noteMsgs[MsgIndex.index(msg[1])][1],
|
noteMsgs[MsgIndex.index(msg[1])][1],
|
||||||
noteMsgs[MsgIndex.index(msg[1])][2],
|
noteMsgs[MsgIndex.index(msg[1])][2],
|
||||||
msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2],
|
msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2],
|
||||||
|
track_number=track_no,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
noteMsgs.pop(MsgIndex.index(msg[1]))
|
noteMsgs.pop(MsgIndex.index(msg[1]))
|
||||||
MsgIndex.pop(MsgIndex.index(msg[1]))
|
MsgIndex.pop(MsgIndex.index(msg[1]))
|
||||||
|
|
||||||
|
del InstID
|
||||||
|
|
||||||
tracks = []
|
tracks = []
|
||||||
cmdAmount = 0
|
cmd_amount = 0
|
||||||
maxScore = 0
|
max_score = 0
|
||||||
CheckFirstChannel = False
|
|
||||||
|
|
||||||
# 临时用的插值计算函数
|
|
||||||
def _linearFun(_note: SingleNote) -> list:
|
|
||||||
"""传入音符数据,返回以半秒为分割的插值列表
|
|
||||||
:param _note: SingleNote 音符
|
|
||||||
:return list[tuple(int开始时间(毫秒), int乐器, int音符, int力度(内置), float音量(播放)),]"""
|
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
totalCount = int(_note.lastTime / 500)
|
|
||||||
|
|
||||||
for _i in range(totalCount):
|
|
||||||
result.append(
|
|
||||||
(
|
|
||||||
_note.startTime + _i * 500,
|
|
||||||
_note.instrument,
|
|
||||||
_note.pitch,
|
|
||||||
_note.velocity,
|
|
||||||
MaxVolume * ((totalCount - _i) / totalCount),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
# 此处 我们把通道视为音轨
|
# 此处 我们把通道视为音轨
|
||||||
for track in note_channels:
|
for no, track in note_channels.items():
|
||||||
# 如果当前通道为空 则跳过
|
# 如果当前通道为空 则跳过
|
||||||
if not track:
|
if not track:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if note_channels.index(track) == 0:
|
SpecialBits = True if no == 9 else False
|
||||||
CheckFirstChannel = True
|
|
||||||
SpecialBits = False
|
|
||||||
elif note_channels.index(track) == 9:
|
|
||||||
SpecialBits = True
|
|
||||||
else:
|
|
||||||
CheckFirstChannel = False
|
|
||||||
SpecialBits = False
|
|
||||||
|
|
||||||
nowTrack = []
|
track_now = []
|
||||||
|
|
||||||
for note in track:
|
for note in track:
|
||||||
for every_note in _linearFun(note):
|
for every_note in self._linear_note(
|
||||||
# 应该是计算的时候出了点小问题
|
note, 50 if note.track_no == 0 else 500
|
||||||
# 我们应该用一个MC帧作为时间单位而不是半秒
|
):
|
||||||
|
|
||||||
if SpecialBits:
|
soundID, _X = (
|
||||||
soundID, _X = self.perc_inst_to_soundID_withX(InstID)
|
self.perc_inst_to_soundID_withX(note.pitch)
|
||||||
else:
|
if SpecialBits
|
||||||
soundID, _X = self.inst_to_souldID_withX(InstID)
|
else self.inst_to_souldID_withX(note.inst)
|
||||||
|
|
||||||
score_now = round(every_note[0] / speed / 50000)
|
|
||||||
|
|
||||||
maxScore = max(maxScore, score_now)
|
|
||||||
|
|
||||||
nowTrack.append(
|
|
||||||
"execute @a[scores={"
|
|
||||||
+ str(scoreboard_name)
|
|
||||||
+ "="
|
|
||||||
+ str(score_now)
|
|
||||||
+ "}"
|
|
||||||
+ f"] ~ ~ ~ playsound {soundID} @s ~ ~{1 / every_note[4] - 1} ~ "
|
|
||||||
f"{note.velocity * (0.7 if CheckFirstChannel else 0.9)} {2 ** ((note.pitch - 60 - _X) / 12)}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cmdAmount += 1
|
score_now = round(every_note[0] / speed / 50)
|
||||||
tracks.append(nowTrack)
|
|
||||||
|
|
||||||
return [tracks, cmdAmount, maxScore]
|
max_score = max(max_score, score_now)
|
||||||
|
mc_pitch = 2 ** ((note.pitch - 60 - _X) / 12)
|
||||||
|
blockmeter = 1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1
|
||||||
|
|
||||||
|
track_now.append(
|
||||||
|
SingleCommand(
|
||||||
|
self.execute_cmd_head.format(
|
||||||
|
"@a[scores=({}={})]".format(scoreboard_name, score_now)
|
||||||
|
.replace("(", r"{")
|
||||||
|
.replace(")", r"}")
|
||||||
|
)
|
||||||
|
+ "playsound {} @s ^ ^ ^{} {} {}".format(
|
||||||
|
soundID,
|
||||||
|
blockmeter,
|
||||||
|
note.velocity / 128,
|
||||||
|
"" if SpecialBits else mc_pitch,
|
||||||
|
),
|
||||||
|
annotation="在{}播放{}%({}BM)的{}音".format(
|
||||||
|
mctick2timestr(score_now),
|
||||||
|
max_volume * 100,
|
||||||
|
blockmeter,
|
||||||
|
"{}:{}".format(soundID, note.pitch),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_amount += 1
|
||||||
|
|
||||||
|
if track_now:
|
||||||
|
self.music_command_list.extend(track_now)
|
||||||
|
tracks.append(track_now)
|
||||||
|
|
||||||
|
self.music_tick_num = max_score
|
||||||
|
return (tracks, cmd_amount, max_score)
|
||||||
|
|
||||||
|
# 简单的单音填充的延迟应用
|
||||||
|
def to_command_list_in_delay(
|
||||||
|
self,
|
||||||
|
max_volume: float = 1.0,
|
||||||
|
speed: float = 1.0,
|
||||||
|
player_selector: str = "@a",
|
||||||
|
) -> Tuple[List[SingleCommand], int]:
|
||||||
|
"""
|
||||||
|
使用金羿的转换思路,使用完全填充算法优化音感后,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
max_volume: float
|
||||||
|
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
||||||
|
speed: float
|
||||||
|
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||||
|
player_selector: str
|
||||||
|
玩家选择器,默认为`@a`
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple( list[SingleCommand,...], int音乐时长游戏刻 )
|
||||||
|
"""
|
||||||
|
|
||||||
|
if speed == 0:
|
||||||
|
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||||
|
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
||||||
|
|
||||||
|
self.to_music_channels()
|
||||||
|
|
||||||
|
note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[])
|
||||||
|
InstID = -1
|
||||||
|
|
||||||
|
# 此处 我们把通道视为音轨
|
||||||
|
for i in self.channels.keys():
|
||||||
|
# 如果当前通道为空 则跳过
|
||||||
|
if not self.channels[i]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# nowChannel = []
|
||||||
|
for track_no, track in self.channels[i].items():
|
||||||
|
|
||||||
|
noteMsgs = []
|
||||||
|
MsgIndex = []
|
||||||
|
|
||||||
|
for msg in track:
|
||||||
|
if msg[0] == "PgmC":
|
||||||
|
InstID = msg[1]
|
||||||
|
|
||||||
|
elif msg[0] == "NoteS":
|
||||||
|
noteMsgs.append(msg[1:])
|
||||||
|
MsgIndex.append(msg[1])
|
||||||
|
|
||||||
|
elif msg[0] == "NoteE":
|
||||||
|
if msg[1] in MsgIndex:
|
||||||
|
note_channels[i].append(
|
||||||
|
SingleNote(
|
||||||
|
InstID,
|
||||||
|
msg[1],
|
||||||
|
noteMsgs[MsgIndex.index(msg[1])][1],
|
||||||
|
noteMsgs[MsgIndex.index(msg[1])][2],
|
||||||
|
msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2],
|
||||||
|
track_number=track_no,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
noteMsgs.pop(MsgIndex.index(msg[1]))
|
||||||
|
MsgIndex.pop(MsgIndex.index(msg[1]))
|
||||||
|
|
||||||
|
del InstID
|
||||||
|
|
||||||
|
tracks = {}
|
||||||
|
InstID = -1
|
||||||
|
# open("RES.TXT", "w", encoding="utf-8").write(str(note_channels))
|
||||||
|
|
||||||
|
# 此处 我们把通道视为音轨
|
||||||
|
for no, track in note_channels.items():
|
||||||
|
# 如果当前通道为空 则跳过
|
||||||
|
if not track:
|
||||||
|
continue
|
||||||
|
|
||||||
|
SpecialBits = True if no == 9 else False
|
||||||
|
|
||||||
|
for note in track:
|
||||||
|
for every_note in self._linear_note(
|
||||||
|
note, 50 if note.track_no == 0 else 500
|
||||||
|
):
|
||||||
|
|
||||||
|
soundID, _X = (
|
||||||
|
self.perc_inst_to_soundID_withX(note.pitch)
|
||||||
|
if SpecialBits
|
||||||
|
else self.inst_to_souldID_withX(note.inst)
|
||||||
|
)
|
||||||
|
|
||||||
|
score_now = round(every_note[0] / speed / 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tracks[score_now].append(
|
||||||
|
self.execute_cmd_head.format(player_selector)
|
||||||
|
+ f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} "
|
||||||
|
+ (
|
||||||
|
""
|
||||||
|
if SpecialBits
|
||||||
|
else f"{2 ** ((note.pitch - 60 - _X) / 12)}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
tracks[score_now] = [
|
||||||
|
self.execute_cmd_head.format(player_selector)
|
||||||
|
+ f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} "
|
||||||
|
+ (
|
||||||
|
""
|
||||||
|
if SpecialBits
|
||||||
|
else f"{2 ** ((note.pitch - 60 - _X) / 12)}"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
all_ticks = list(tracks.keys())
|
||||||
|
all_ticks.sort()
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for i in range(len(all_ticks)):
|
||||||
|
for j in range(len(tracks[all_ticks[i]])):
|
||||||
|
results.append(
|
||||||
|
SingleCommand(
|
||||||
|
tracks[all_ticks[i]][j],
|
||||||
|
tick_delay=(
|
||||||
|
0
|
||||||
|
if j != 0
|
||||||
|
else (
|
||||||
|
all_ticks[i] - all_ticks[i - 1]
|
||||||
|
if i != 0
|
||||||
|
else all_ticks[i]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
annotation="在{}播放{}%的{}音".format(
|
||||||
|
mctick2timestr(i), max_volume * 100, ""
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.music_command_list = results
|
||||||
|
self.music_tick_num = max(all_ticks)
|
||||||
|
return results, self.music_tick_num
|
||||||
|
|
||||||
|
|
||||||
|
class FutureMidiConvertM5(MidiConvert):
|
||||||
|
"""
|
||||||
|
加入同刻偏移算法优化音感
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 神奇的偏移音
|
||||||
|
def to_command_list_in_delay(
|
||||||
|
self,
|
||||||
|
max_volume: float = 1.0,
|
||||||
|
speed: float = 1.0,
|
||||||
|
player_selector: str = "@a",
|
||||||
|
) -> Tuple[List[SingleCommand], int]:
|
||||||
|
"""
|
||||||
|
使用金羿的转换思路,使用同刻偏移算法优化音感后,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
max_volume: float
|
||||||
|
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
||||||
|
speed: float
|
||||||
|
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||||
|
player_selector: str
|
||||||
|
玩家选择器,默认为`@a`
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple( list[SingleCommand,...], int音乐时长游戏刻 )
|
||||||
|
"""
|
||||||
|
|
||||||
|
if speed == 0:
|
||||||
|
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||||
|
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
||||||
|
|
||||||
|
self.to_music_channels()
|
||||||
|
|
||||||
|
tracks = {}
|
||||||
|
InstID = -1
|
||||||
|
|
||||||
|
# 此处 我们把通道视为音轨
|
||||||
|
for i in self.channels.keys():
|
||||||
|
# 如果当前通道为空 则跳过
|
||||||
|
if not self.channels[i]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 第十通道是打击乐通道
|
||||||
|
SpecialBits = True if i == 9 else False
|
||||||
|
|
||||||
|
# nowChannel = []
|
||||||
|
|
||||||
|
for track_no, track in self.channels[i].items():
|
||||||
|
for msg in track:
|
||||||
|
if msg[0] == "PgmC":
|
||||||
|
InstID = msg[1]
|
||||||
|
|
||||||
|
elif msg[0] == "NoteS":
|
||||||
|
soundID, _X = (
|
||||||
|
self.perc_inst_to_soundID_withX(msg[1])
|
||||||
|
if SpecialBits
|
||||||
|
else self.inst_to_souldID_withX(InstID)
|
||||||
|
)
|
||||||
|
|
||||||
|
score_now = round(msg[-1] / float(speed) / 50)
|
||||||
|
# print(score_now)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tracks[score_now].append(
|
||||||
|
self.execute_cmd_head.format(player_selector)
|
||||||
|
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
|
||||||
|
+ (
|
||||||
|
""
|
||||||
|
if SpecialBits
|
||||||
|
else f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
tracks[score_now] = [
|
||||||
|
self.execute_cmd_head.format(player_selector)
|
||||||
|
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
|
||||||
|
+ (
|
||||||
|
""
|
||||||
|
if SpecialBits
|
||||||
|
else f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
all_ticks = list(tracks.keys())
|
||||||
|
all_ticks.sort()
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for i in range(len(all_ticks)):
|
||||||
|
for j in range(len(tracks[all_ticks[i]])):
|
||||||
|
results.append(
|
||||||
|
SingleCommand(
|
||||||
|
tracks[all_ticks[i]][j],
|
||||||
|
tick_delay=(
|
||||||
|
(
|
||||||
|
0
|
||||||
|
if (
|
||||||
|
(all_ticks[i + 1] - all_ticks[i])
|
||||||
|
/ len(tracks[all_ticks[i]])
|
||||||
|
< 1
|
||||||
|
)
|
||||||
|
else 1
|
||||||
|
)
|
||||||
|
if j != 0
|
||||||
|
else (
|
||||||
|
(
|
||||||
|
all_ticks[i]
|
||||||
|
- all_ticks[i - 1]
|
||||||
|
- (
|
||||||
|
0
|
||||||
|
if (
|
||||||
|
(all_ticks[i] - all_ticks[i - 1])
|
||||||
|
/ len(tracks[all_ticks[i - 1]])
|
||||||
|
< 1
|
||||||
|
)
|
||||||
|
else (len(tracks[all_ticks[i - 1]]) - 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if i != 0
|
||||||
|
else all_ticks[i]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
annotation="在{}播放{}%的{}音".format(
|
||||||
|
mctick2timestr(
|
||||||
|
i + 0
|
||||||
|
if (
|
||||||
|
(all_ticks[i + 1] - all_ticks[i])
|
||||||
|
/ len(tracks[all_ticks[i]])
|
||||||
|
< 1
|
||||||
|
)
|
||||||
|
else j
|
||||||
|
),
|
||||||
|
max_volume * 100,
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.music_command_list = results
|
||||||
|
self.music_tick_num = max(all_ticks)
|
||||||
|
return results, self.music_tick_num
|
||||||
|
|
||||||
|
|
||||||
|
class FutureMidiConvertM6(MidiConvert):
|
||||||
|
"""
|
||||||
|
加入插值算法优化音感,但仅用于第一音轨
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO 没写完的!!!!
|
||||||
|
@ -19,22 +19,48 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
import os
|
# BUG退散!BUG退散! BUG退散!BUG退散!
|
||||||
|
# 异常、错误作乱之时 異常、誤りが、困った時は
|
||||||
|
# 二六字组!万国码合!二六字组!万国码合! グループ!コード#!グループ!コード#!
|
||||||
|
# 赶快呼叫 程序员!Let's Go! 直ぐに呼びましょプログラマ レッツゴー!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import math
|
import math
|
||||||
from typing import Tuple, List, Union
|
import os
|
||||||
|
from typing import List, Literal, Tuple, Union
|
||||||
|
|
||||||
import mido
|
import mido
|
||||||
|
|
||||||
from .exceptions import *
|
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .utils import *
|
from .exceptions import *
|
||||||
from .subclass import *
|
from .subclass import *
|
||||||
|
from .utils import *
|
||||||
|
|
||||||
VM = TypeVar("VM", mido.MidiFile, None) # void mido
|
VoidMido = Union[mido.MidiFile, None] # void mido
|
||||||
"""
|
"""
|
||||||
空Midi类类型
|
空Midi类类型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ChannelType = Dict[
|
||||||
|
int,
|
||||||
|
Dict[
|
||||||
|
int,
|
||||||
|
List[
|
||||||
|
Union[
|
||||||
|
Tuple[Literal["PgmC"], int, int],
|
||||||
|
Tuple[Literal["NoteS"], int, int, int],
|
||||||
|
Tuple[Literal["NoteE"], int, int],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
以字典所标记的频道信息类型
|
||||||
|
|
||||||
|
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
|
||||||
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
学习笔记:
|
学习笔记:
|
||||||
tempo: microseconds per quarter note 毫秒每四分音符,换句话说就是一拍占多少毫秒
|
tempo: microseconds per quarter note 毫秒每四分音符,换句话说就是一拍占多少毫秒
|
||||||
@ -75,7 +101,7 @@ class MidiConvert:
|
|||||||
将Midi文件转换为我的世界内容
|
将Midi文件转换为我的世界内容
|
||||||
"""
|
"""
|
||||||
|
|
||||||
midi: VM
|
midi: VoidMido
|
||||||
"""MidiFile对象"""
|
"""MidiFile对象"""
|
||||||
|
|
||||||
midi_music_name: str
|
midi_music_name: str
|
||||||
@ -87,7 +113,7 @@ class MidiConvert:
|
|||||||
execute_cmd_head: str
|
execute_cmd_head: str
|
||||||
"""execute指令头部"""
|
"""execute指令头部"""
|
||||||
|
|
||||||
channels: Dict[int, Dict[int, List[Tuple[str, int, int, Union[None, int]]]]]
|
channels: ChannelType
|
||||||
"""频道信息字典"""
|
"""频道信息字典"""
|
||||||
|
|
||||||
music_command_list: List[SingleCommand]
|
music_command_list: List[SingleCommand]
|
||||||
@ -101,7 +127,7 @@ class MidiConvert:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
midi_obj: VM,
|
midi_obj: VoidMido,
|
||||||
midi_name: str,
|
midi_name: str,
|
||||||
enable_old_exe_format: bool = False,
|
enable_old_exe_format: bool = False,
|
||||||
):
|
):
|
||||||
@ -118,7 +144,7 @@ class MidiConvert:
|
|||||||
是否启用旧版(≤1.19)指令格式,默认为否
|
是否启用旧版(≤1.19)指令格式,默认为否
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.midi: VM = midi_obj
|
self.midi: VoidMido = midi_obj
|
||||||
|
|
||||||
self.midi_music_name: str = midi_name
|
self.midi_music_name: str = midi_name
|
||||||
|
|
||||||
@ -151,7 +177,9 @@ class MidiConvert:
|
|||||||
是否启用旧版(≤1.19)指令格式,默认为否
|
是否启用旧版(≤1.19)指令格式,默认为否
|
||||||
"""
|
"""
|
||||||
|
|
||||||
midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0].replace(' ','_')
|
midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0].replace(
|
||||||
|
" ", "_"
|
||||||
|
)
|
||||||
"""文件名,不含路径且不含后缀"""
|
"""文件名,不含路径且不含后缀"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -491,48 +519,43 @@ class MidiConvert:
|
|||||||
|
|
||||||
def to_music_channels(
|
def to_music_channels(
|
||||||
self,
|
self,
|
||||||
) -> Dict[int, Dict[int, List[Tuple[str, int, int, Union[None, int]]]]]:
|
) -> ChannelType:
|
||||||
"""
|
"""
|
||||||
使用金羿的转换思路,将midi解析并转换为频道信息
|
使用金羿的转换思路,将midi解析并转换为频道信息字典
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[int, Dict[int, List[Tuple[str,int,int,Union[None,int]]]]]
|
以频道作为分割的Midi信息字典:
|
||||||
|
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
|
||||||
"""
|
"""
|
||||||
|
if self.midi is None:
|
||||||
|
raise MidiUnboundError(
|
||||||
|
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。"
|
||||||
|
)
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
midi_channels = empty_midi_channels()
|
midi_channels: ChannelType = empty_midi_channels()
|
||||||
|
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||||
|
|
||||||
|
# a = 0
|
||||||
# 我们来用通道统计音乐信息
|
# 我们来用通道统计音乐信息
|
||||||
# 但是是用分轨的思路的
|
# 但是是用分轨的思路的
|
||||||
for track_no, track in enumerate(self.midi.tracks):
|
for track_no, track in enumerate(self.midi.tracks):
|
||||||
|
# print(track_no,track)
|
||||||
microseconds = 0
|
microseconds = 0
|
||||||
|
if not track:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# print(track_no,"="*20)
|
||||||
for msg in track:
|
for msg in track:
|
||||||
|
# print("+++",msg)
|
||||||
if msg.time != 0:
|
if msg.time != 0:
|
||||||
try:
|
microseconds += msg.time * tempo / self.midi.ticks_per_beat / 1000
|
||||||
microseconds += (
|
|
||||||
msg.time * tempo / self.midi.ticks_per_beat / 1000
|
|
||||||
)
|
|
||||||
# print(microseconds)
|
|
||||||
except NameError:
|
|
||||||
# raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
|
|
||||||
microseconds += (
|
|
||||||
msg.time
|
|
||||||
* mido.midifiles.midifiles.DEFAULT_TEMPO
|
|
||||||
/ self.midi.ticks_per_beat
|
|
||||||
) / 1000
|
|
||||||
|
|
||||||
if msg.is_meta:
|
if msg.is_meta:
|
||||||
if msg.type == "set_tempo":
|
if msg.type == "set_tempo":
|
||||||
tempo = msg.tempo
|
tempo = msg.tempo
|
||||||
else:
|
else:
|
||||||
# 曾用于调试模式
|
|
||||||
# try:
|
|
||||||
# if msg.channel > 15:
|
|
||||||
# raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
|
|
||||||
# except AttributeError:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
if not track_no in midi_channels[msg.channel].keys():
|
if not track_no in midi_channels[msg.channel].keys():
|
||||||
midi_channels[msg.channel][track_no] = []
|
midi_channels[msg.channel][track_no] = []
|
||||||
@ -545,6 +568,7 @@ class MidiConvert:
|
|||||||
midi_channels[msg.channel][track_no].append(
|
midi_channels[msg.channel][track_no].append(
|
||||||
("NoteS", msg.note, msg.velocity, microseconds)
|
("NoteS", msg.note, msg.velocity, microseconds)
|
||||||
)
|
)
|
||||||
|
# a+=1
|
||||||
|
|
||||||
elif (msg.type == "note_on" and msg.velocity == 0) or (
|
elif (msg.type == "note_on" and msg.velocity == 0) or (
|
||||||
msg.type == "note_off"
|
msg.type == "note_off"
|
||||||
@ -553,6 +577,8 @@ class MidiConvert:
|
|||||||
("NoteE", msg.note, microseconds)
|
("NoteE", msg.note, microseconds)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# print(a)
|
||||||
|
|
||||||
"""整合后的音乐通道格式
|
"""整合后的音乐通道格式
|
||||||
每个通道包括若干消息元素其中逃不过这三种:
|
每个通道包括若干消息元素其中逃不过这三种:
|
||||||
|
|
||||||
@ -563,10 +589,11 @@ class MidiConvert:
|
|||||||
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
|
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
|
||||||
|
|
||||||
3 音符结束消息
|
3 音符结束消息
|
||||||
("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
|
("NoteE", 结束的音符ID, 距离演奏开始的毫秒)"""
|
||||||
|
del tempo, self.channels
|
||||||
self.channels = midi_channels
|
self.channels = midi_channels
|
||||||
return self.channels
|
# [print([print(no,tno,sum([True if i[0] == 'NoteS' else False for i in track])) for tno,track in cna.items()]) if cna else False for no,cna in midi_channels.items()]
|
||||||
|
return midi_channels
|
||||||
|
|
||||||
def to_command_list_in_score(
|
def to_command_list_in_score(
|
||||||
self,
|
self,
|
||||||
@ -598,6 +625,7 @@ class MidiConvert:
|
|||||||
tracks = []
|
tracks = []
|
||||||
cmdAmount = 0
|
cmdAmount = 0
|
||||||
maxScore = 0
|
maxScore = 0
|
||||||
|
InstID = -1
|
||||||
|
|
||||||
self.to_music_channels()
|
self.to_music_channels()
|
||||||
|
|
||||||
@ -620,21 +648,14 @@ class MidiConvert:
|
|||||||
InstID = msg[1]
|
InstID = msg[1]
|
||||||
|
|
||||||
elif msg[0] == "NoteS":
|
elif msg[0] == "NoteS":
|
||||||
try:
|
|
||||||
soundID, _X = (
|
soundID, _X = (
|
||||||
self.perc_inst_to_soundID_withX(InstID)
|
self.perc_inst_to_soundID_withX(msg[1])
|
||||||
if SpecialBits
|
if SpecialBits
|
||||||
else self.inst_to_souldID_withX(InstID)
|
else self.inst_to_souldID_withX(InstID)
|
||||||
)
|
)
|
||||||
except UnboundLocalError as E:
|
|
||||||
# raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
|
|
||||||
soundID, _X = (
|
|
||||||
self.perc_inst_to_soundID_withX(-1)
|
|
||||||
if SpecialBits
|
|
||||||
else self.inst_to_souldID_withX(-1)
|
|
||||||
)
|
|
||||||
score_now = round(msg[-1] / float(speed) / 50)
|
score_now = round(msg[-1] / float(speed) / 50)
|
||||||
maxScore = max(maxScore, score_now)
|
maxScore = max(maxScore, score_now)
|
||||||
|
mc_pitch = "" if SpecialBits else 2 ** ((msg[1] - 60 - _X) / 12)
|
||||||
|
|
||||||
nowTrack.append(
|
nowTrack.append(
|
||||||
SingleCommand(
|
SingleCommand(
|
||||||
@ -645,10 +666,13 @@ class MidiConvert:
|
|||||||
.replace("(", r"{")
|
.replace("(", r"{")
|
||||||
.replace(")", r"}")
|
.replace(")", r"}")
|
||||||
)
|
)
|
||||||
+ f"playsound {soundID} @s ^ ^ ^{1 / max_volume - 1} {msg[2] / 128} "
|
+ "playsound {} @s ^ ^ ^{} {} {}".format(
|
||||||
f"{2 ** ((msg[1] - 60 - _X) / 12)}",
|
soundID, 1 / max_volume - 1, msg[2] / 128, mc_pitch
|
||||||
|
),
|
||||||
annotation="在{}播放{}%的{}音".format(
|
annotation="在{}播放{}%的{}音".format(
|
||||||
mctick2timestr(score_now), max_volume * 100, ""
|
mctick2timestr(score_now),
|
||||||
|
max_volume * 100,
|
||||||
|
"{}:{}".format(soundID, mc_pitch),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -659,6 +683,8 @@ class MidiConvert:
|
|||||||
self.music_command_list.extend(nowTrack)
|
self.music_command_list.extend(nowTrack)
|
||||||
tracks.append(nowTrack)
|
tracks.append(nowTrack)
|
||||||
|
|
||||||
|
# print(cmdAmount)
|
||||||
|
del InstID
|
||||||
self.music_tick_num = maxScore
|
self.music_tick_num = maxScore
|
||||||
return (tracks, cmdAmount, maxScore)
|
return (tracks, cmdAmount, maxScore)
|
||||||
|
|
||||||
@ -692,6 +718,8 @@ class MidiConvert:
|
|||||||
self.to_music_channels()
|
self.to_music_channels()
|
||||||
|
|
||||||
tracks = {}
|
tracks = {}
|
||||||
|
InstID = -1
|
||||||
|
# cmd_amount = 0
|
||||||
|
|
||||||
# 此处 我们把通道视为音轨
|
# 此处 我们把通道视为音轨
|
||||||
for i in self.channels.keys():
|
for i in self.channels.keys():
|
||||||
@ -710,21 +738,13 @@ class MidiConvert:
|
|||||||
InstID = msg[1]
|
InstID = msg[1]
|
||||||
|
|
||||||
elif msg[0] == "NoteS":
|
elif msg[0] == "NoteS":
|
||||||
try:
|
|
||||||
soundID, _X = (
|
soundID, _X = (
|
||||||
self.perc_inst_to_soundID_withX(msg[1])
|
self.perc_inst_to_soundID_withX(msg[1])
|
||||||
if SpecialBits
|
if SpecialBits
|
||||||
else self.inst_to_souldID_withX(InstID)
|
else self.inst_to_souldID_withX(InstID)
|
||||||
)
|
)
|
||||||
except UnboundLocalError as E:
|
|
||||||
# raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
|
|
||||||
soundID, _X = (
|
|
||||||
self.perc_inst_to_soundID_withX(-1)
|
|
||||||
if SpecialBits
|
|
||||||
else self.inst_to_souldID_withX(-1)
|
|
||||||
)
|
|
||||||
score_now = round(msg[-1] / float(speed) / 50)
|
score_now = round(msg[-1] / float(speed) / 50)
|
||||||
# print(score_now)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tracks[score_now].append(
|
tracks[score_now].append(
|
||||||
@ -747,6 +767,11 @@ class MidiConvert:
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# cmd_amount += 1
|
||||||
|
|
||||||
|
# print(cmd_amount)
|
||||||
|
|
||||||
|
del InstID
|
||||||
all_ticks = list(tracks.keys())
|
all_ticks = list(tracks.keys())
|
||||||
all_ticks.sort()
|
all_ticks.sort()
|
||||||
results = []
|
results = []
|
||||||
@ -773,83 +798,7 @@ class MidiConvert:
|
|||||||
|
|
||||||
self.music_command_list = results
|
self.music_command_list = results
|
||||||
self.music_tick_num = max(all_ticks)
|
self.music_tick_num = max(all_ticks)
|
||||||
return [results, self.music_tick_num]
|
return results, self.music_tick_num
|
||||||
|
|
||||||
|
|
||||||
def to_dict(
|
|
||||||
self,
|
|
||||||
) -> dict:
|
|
||||||
"""
|
|
||||||
使用金羿的转换思路,将midi转换为字典
|
|
||||||
:return: dict()
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
|
||||||
channels = empty_midi_channels()
|
|
||||||
|
|
||||||
# 我们来用通道统计音乐信息
|
|
||||||
# 但是是用分轨的思路的
|
|
||||||
for track_no, track in enumerate(self.midi.tracks):
|
|
||||||
microseconds = 0
|
|
||||||
|
|
||||||
for msg in track:
|
|
||||||
if msg.time != 0:
|
|
||||||
try:
|
|
||||||
microseconds += (
|
|
||||||
msg.time * tempo / self.midi.ticks_per_beat / 1000
|
|
||||||
)
|
|
||||||
# print(microseconds)
|
|
||||||
except NameError:
|
|
||||||
# raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
|
|
||||||
microseconds += (
|
|
||||||
msg.time
|
|
||||||
* mido.midifiles.midifiles.DEFAULT_TEMPO
|
|
||||||
/ self.midi.ticks_per_beat
|
|
||||||
) / 1000
|
|
||||||
|
|
||||||
if msg.is_meta:
|
|
||||||
if msg.type == "set_tempo":
|
|
||||||
tempo = msg.tempo
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
# 曾用于调试模式
|
|
||||||
# if msg.channel > 15:
|
|
||||||
# raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
|
|
||||||
if not track_no in channels[msg.channel].keys():
|
|
||||||
channels[msg.channel][track_no] = []
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if msg.type == "program_change":
|
|
||||||
channels[msg.channel][track_no].append(
|
|
||||||
("PgmC", msg.program, microseconds)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif msg.type == "note_on" and msg.velocity != 0:
|
|
||||||
channels[msg.channel][track_no].append(
|
|
||||||
("NoteS", msg.note, msg.velocity, microseconds)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif (msg.type == "note_on" and msg.velocity == 0) or (
|
|
||||||
msg.type == "note_off"
|
|
||||||
):
|
|
||||||
channels[msg.channel][track_no].append(
|
|
||||||
("NoteE", msg.note, microseconds)
|
|
||||||
)
|
|
||||||
|
|
||||||
"""整合后的音乐通道格式
|
|
||||||
每个通道包括若干消息元素其中逃不过这三种:
|
|
||||||
|
|
||||||
1 切换乐器消息
|
|
||||||
("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
|
|
||||||
|
|
||||||
2 音符开始消息
|
|
||||||
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
|
|
||||||
|
|
||||||
3 音符结束消息
|
|
||||||
("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
|
|
||||||
|
|
||||||
return channels
|
|
||||||
|
|
||||||
def copy_important(self):
|
def copy_important(self):
|
||||||
dst = MidiConvert(
|
dst = MidiConvert(
|
||||||
|
@ -14,7 +14,9 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__all__ = []
|
__all__ = [
|
||||||
|
"ConvertConfig",
|
||||||
|
]
|
||||||
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"))
|
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"))
|
||||||
|
|
||||||
from .main import *
|
from .main import *
|
@ -20,7 +20,8 @@ Terms & Conditions: License.md in the root directory
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import zipfile
|
import zipfile
|
||||||
from typing import List
|
import datetime
|
||||||
|
from typing import List, Union, Literal
|
||||||
|
|
||||||
|
|
||||||
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
|
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
|
||||||
@ -46,16 +47,23 @@ def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
|
|||||||
|
|
||||||
def behavior_mcpack_manifest(
|
def behavior_mcpack_manifest(
|
||||||
pack_description: str = "",
|
pack_description: str = "",
|
||||||
pack_version: List[int] = [0, 0, 1],
|
pack_version: Union[List[int], Literal[None]] = None,
|
||||||
pack_name: str = "",
|
pack_name: str = "",
|
||||||
pack_uuid: str = None,
|
pack_uuid: Union[str, Literal[None]] = None,
|
||||||
modules_description: str = "",
|
modules_description: str = "",
|
||||||
modules_version: List[int] = [0, 0, 1],
|
modules_version: List[int] = [0, 0, 1],
|
||||||
modules_uuid: str = None,
|
modules_uuid: Union[str, Literal[None]] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
生成一个我的世界行为包组件的定义清单文件
|
生成一个我的世界行为包组件的定义清单文件
|
||||||
"""
|
"""
|
||||||
|
if not pack_version:
|
||||||
|
now_date = datetime.datetime.now()
|
||||||
|
pack_version = [
|
||||||
|
now_date.year,
|
||||||
|
now_date.month * 100 + now_date.day,
|
||||||
|
now_date.hour * 100 + now_date.minute,
|
||||||
|
]
|
||||||
return {
|
return {
|
||||||
"format_version": 1,
|
"format_version": 1,
|
||||||
"header": {
|
"header": {
|
||||||
|
@ -14,7 +14,10 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__all__ = []
|
__all__ = [
|
||||||
|
"to_BDX_file_in_score",
|
||||||
|
"to_BDX_file_in_delay"
|
||||||
|
]
|
||||||
__author__ = (("金羿", "Eilles Wan"),)
|
__author__ = (("金羿", "Eilles Wan"),)
|
||||||
|
|
||||||
from .main import *
|
from .main import *
|
||||||
|
@ -13,12 +13,14 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import brotli
|
import brotli
|
||||||
|
|
||||||
from ...main import MidiConvert
|
from ...main import MidiConvert
|
||||||
from ..bdx import commands_to_BDX_bytes, bdx_move, x, y, z, form_command_block_in_BDX_bytes
|
|
||||||
from ..main import ConvertConfig
|
|
||||||
from ...subclass import SingleCommand
|
from ...subclass import SingleCommand
|
||||||
|
from ..bdx import (bdx_move, commands_to_BDX_bytes,
|
||||||
|
form_command_block_in_BDX_bytes, x, y, z)
|
||||||
|
from ..main import ConvertConfig
|
||||||
|
|
||||||
|
|
||||||
def to_BDX_file_in_score(
|
def to_BDX_file_in_score(
|
||||||
@ -169,7 +171,6 @@ def to_BDX_file_in_delay(
|
|||||||
b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00"
|
b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
cmdBytes, size, finalPos = commands_to_BDX_bytes(cmdlist, max_height - 1)
|
cmdBytes, size, finalPos = commands_to_BDX_bytes(cmdlist, max_height - 1)
|
||||||
|
|
||||||
if data_cfg.progressbar_style:
|
if data_cfg.progressbar_style:
|
||||||
|
@ -14,7 +14,9 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__all__ = []
|
__all__ = [
|
||||||
|
"to_function_addon_in_score"
|
||||||
|
]
|
||||||
__author__ = (("金羿", "Eilles Wan"),)
|
__author__ = (("金羿", "Eilles Wan"),)
|
||||||
|
|
||||||
from .main import *
|
from .main import *
|
||||||
|
@ -56,13 +56,12 @@ def to_function_addon_in_score(
|
|||||||
shutil.rmtree(f"{data_cfg.dist_path}/temp/functions/")
|
shutil.rmtree(f"{data_cfg.dist_path}/temp/functions/")
|
||||||
os.makedirs(f"{data_cfg.dist_path}/temp/functions/mscplay")
|
os.makedirs(f"{data_cfg.dist_path}/temp/functions/mscplay")
|
||||||
|
|
||||||
|
|
||||||
# 写入manifest.json
|
# 写入manifest.json
|
||||||
with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
|
with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
|
||||||
json.dump(
|
json.dump(
|
||||||
behavior_mcpack_manifest(
|
behavior_mcpack_manifest(
|
||||||
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包,MCFUNCTION(MCPACK) 计分播放器 - 由 音·创 生成",
|
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包,MCFUNCTION(MCPACK) 计分播放器 - 由 音·创 生成",
|
||||||
pack_name=midi_cvt.midi_music_name+"播放",
|
pack_name=midi_cvt.midi_music_name + "播放",
|
||||||
modules_description=f"无 - 由 音·创 生成",
|
modules_description=f"无 - 由 音·创 生成",
|
||||||
),
|
),
|
||||||
fp=f,
|
fp=f,
|
||||||
|
@ -17,7 +17,7 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Tuple, Union
|
from typing import Tuple, Union, Literal
|
||||||
|
|
||||||
from ..constants import DEFAULT_PROGRESSBAR_STYLE
|
from ..constants import DEFAULT_PROGRESSBAR_STYLE
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ class ConvertConfig:
|
|||||||
speed_multiplier: float
|
speed_multiplier: float
|
||||||
"""速度倍率"""
|
"""速度倍率"""
|
||||||
|
|
||||||
progressbar_style: Tuple[str, Tuple[str, str]]
|
progressbar_style: Union[Tuple[str, Tuple[str, str]], Literal[None]]
|
||||||
"""进度条样式组"""
|
"""进度条样式组"""
|
||||||
|
|
||||||
dist_path: str
|
dist_path: str
|
||||||
|
@ -14,7 +14,9 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__all__ = []
|
__all__ = [
|
||||||
|
"to_mcstructure_file_in_delay"
|
||||||
|
]
|
||||||
__author__ = (("金羿", "Eilles Wan"),)
|
__author__ = (("金羿", "Eilles Wan"),)
|
||||||
|
|
||||||
from .main import *
|
from .main import *
|
||||||
|
@ -71,5 +71,6 @@ def to_mcstructure_file_in_delay(
|
|||||||
|
|
||||||
def to_mcstructure_file_in_redstone(
|
def to_mcstructure_file_in_redstone(
|
||||||
midi_cvt: MidiConvert,
|
midi_cvt: MidiConvert,
|
||||||
data_cfg: ConvertConfig,):
|
data_cfg: ConvertConfig,
|
||||||
|
):
|
||||||
pass
|
pass
|
@ -14,7 +14,9 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__all__ = []
|
__all__ = [
|
||||||
|
"to_mcstructure_addon_in_delay"
|
||||||
|
]
|
||||||
__author__ = (("金羿", "Eilles Wan"),)
|
__author__ = (("金羿", "Eilles Wan"),)
|
||||||
|
|
||||||
from .main import *
|
from .main import *
|
||||||
|
@ -21,8 +21,7 @@ from ...exceptions import CommandFormatError
|
|||||||
from ...main import MidiConvert
|
from ...main import MidiConvert
|
||||||
from ..archive import behavior_mcpack_manifest, compress_zipfile
|
from ..archive import behavior_mcpack_manifest, compress_zipfile
|
||||||
from ..main import ConvertConfig
|
from ..main import ConvertConfig
|
||||||
from ..mcstructure import (commands_to_structure,
|
from ..mcstructure import commands_to_structure, form_command_block_in_NBT_struct
|
||||||
form_command_block_in_NBT_struct)
|
|
||||||
|
|
||||||
|
|
||||||
def to_mcstructure_addon_in_delay(
|
def to_mcstructure_addon_in_delay(
|
||||||
@ -73,7 +72,7 @@ def to_mcstructure_addon_in_delay(
|
|||||||
json.dump(
|
json.dump(
|
||||||
behavior_mcpack_manifest(
|
behavior_mcpack_manifest(
|
||||||
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包,MCSTRUCTURE(MCPACK) 延迟播放器 - 由 音·创 生成",
|
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包,MCSTRUCTURE(MCPACK) 延迟播放器 - 由 音·创 生成",
|
||||||
pack_name=midi_cvt.midi_music_name+"播放",
|
pack_name=midi_cvt.midi_music_name + "播放",
|
||||||
modules_description=f"无 - 由 音·创 生成",
|
modules_description=f"无 - 由 音·创 生成",
|
||||||
),
|
),
|
||||||
fp=f,
|
fp=f,
|
||||||
|
@ -16,7 +16,7 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
from typing import List
|
from typing import List, Tuple
|
||||||
|
|
||||||
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
|
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
|
||||||
|
|
||||||
@ -25,7 +25,10 @@ from .common import bottem_side_length_of_smallest_square_bottom_box
|
|||||||
|
|
||||||
|
|
||||||
def form_note_block_in_NBT_struct(
|
def form_note_block_in_NBT_struct(
|
||||||
note: int, coordinate: tuple, instrument: str = "note.harp", powered: bool = False
|
note: int,
|
||||||
|
coordinate: Tuple[int, int, int],
|
||||||
|
instrument: str = "note.harp",
|
||||||
|
powered: bool = False,
|
||||||
):
|
):
|
||||||
"""生成音符盒方块
|
"""生成音符盒方块
|
||||||
:param note: `int`(0~24)
|
:param note: `int`(0~24)
|
||||||
@ -54,7 +57,7 @@ def form_note_block_in_NBT_struct(
|
|||||||
"x": coordinate[0],
|
"x": coordinate[0],
|
||||||
"y": coordinate[1],
|
"y": coordinate[1],
|
||||||
"z": coordinate[2],
|
"z": coordinate[2],
|
||||||
}
|
} # type: ignore
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -162,7 +165,7 @@ def form_command_block_in_NBT_struct(
|
|||||||
"x": coordinate[0],
|
"x": coordinate[0],
|
||||||
"y": coordinate[1],
|
"y": coordinate[1],
|
||||||
"z": coordinate[2],
|
"z": coordinate[2],
|
||||||
}
|
} # type: ignore
|
||||||
},
|
},
|
||||||
compability_version=17959425,
|
compability_version=17959425,
|
||||||
)
|
)
|
||||||
|
326
Musicreater/previous.py
Normal file
326
Musicreater/previous.py
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
旧版本转换功能以及已经弃用的函数
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2023 音·创 开发者
|
||||||
|
Copyright © 2023 all the developers of Musicreater
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿穆组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from typing import Dict, List, Tuple, Union
|
||||||
|
|
||||||
|
from .exceptions import *
|
||||||
|
from .main import MidiConvert, mido
|
||||||
|
from .subclass import *
|
||||||
|
from .utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class ObsoleteMidiConvert(MidiConvert):
|
||||||
|
"""
|
||||||
|
我说一句话:
|
||||||
|
这些破烂老代码能跑得起来就是谢天谢地,你们还指望我怎么样?这玩意真的不会再维护了,我发誓!
|
||||||
|
"""
|
||||||
|
|
||||||
|
def to_command_list_method1(
|
||||||
|
self,
|
||||||
|
scoreboard_name: str = "mscplay",
|
||||||
|
MaxVolume: float = 1.0,
|
||||||
|
speed: float = 1.0,
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表
|
||||||
|
:param scoreboard_name: 我的世界的计分板名称
|
||||||
|
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||||
|
:return: tuple(命令列表, 命令个数, 计分板最大值)
|
||||||
|
"""
|
||||||
|
# :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
||||||
|
tracks = []
|
||||||
|
if speed == 0:
|
||||||
|
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||||
|
if not self.midi:
|
||||||
|
raise MidiUnboundError(
|
||||||
|
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。"
|
||||||
|
)
|
||||||
|
|
||||||
|
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
|
||||||
|
commands = 0
|
||||||
|
maxscore = 0
|
||||||
|
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||||
|
|
||||||
|
# 分轨的思路其实并不好,但这个算法就是这样
|
||||||
|
# 所以我建议用第二个方法 _toCmdList_m2
|
||||||
|
for i, track in enumerate(self.midi.tracks):
|
||||||
|
ticks = 0
|
||||||
|
instrumentID = 0
|
||||||
|
singleTrack = []
|
||||||
|
|
||||||
|
for msg in track:
|
||||||
|
ticks += msg.time
|
||||||
|
if msg.is_meta:
|
||||||
|
if msg.type == "set_tempo":
|
||||||
|
tempo = msg.tempo
|
||||||
|
else:
|
||||||
|
if msg.type == "program_change":
|
||||||
|
instrumentID = msg.program
|
||||||
|
|
||||||
|
if msg.type == "note_on" and msg.velocity != 0:
|
||||||
|
nowscore = round(
|
||||||
|
(ticks * tempo)
|
||||||
|
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
|
||||||
|
)
|
||||||
|
maxscore = max(maxscore, nowscore)
|
||||||
|
if msg.channel == 9:
|
||||||
|
soundID, _X = self.perc_inst_to_soundID_withX(instrumentID)
|
||||||
|
else:
|
||||||
|
soundID, _X = self.inst_to_souldID_withX(instrumentID)
|
||||||
|
|
||||||
|
singleTrack.append(
|
||||||
|
"execute @a[scores={"
|
||||||
|
+ str(scoreboard_name)
|
||||||
|
+ "="
|
||||||
|
+ str(nowscore)
|
||||||
|
+ "}"
|
||||||
|
+ f"] ~ ~ ~ playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
|
||||||
|
f"{2 ** ((msg.note - 60 - _X) / 12)}"
|
||||||
|
)
|
||||||
|
commands += 1
|
||||||
|
|
||||||
|
if len(singleTrack) != 0:
|
||||||
|
tracks.append(singleTrack)
|
||||||
|
|
||||||
|
return [tracks, commands, maxscore]
|
||||||
|
|
||||||
|
# 原本这个算法的转换效果应该和上面的算法相似的
|
||||||
|
def _toCmdList_m2(
|
||||||
|
self: MidiConvert,
|
||||||
|
scoreboard_name: str = "mscplay",
|
||||||
|
MaxVolume: float = 1.0,
|
||||||
|
speed: float = 1.0,
|
||||||
|
) -> tuple:
|
||||||
|
"""
|
||||||
|
使用神羽和金羿的转换思路,将midi转换为我的世界命令列表
|
||||||
|
:param scoreboard_name: 我的世界的计分板名称
|
||||||
|
:param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
||||||
|
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||||
|
:return: tuple(命令列表, 命令个数, 计分板最大值)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if speed == 0:
|
||||||
|
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||||
|
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
|
||||||
|
|
||||||
|
tracks = []
|
||||||
|
cmdAmount = 0
|
||||||
|
maxScore = 0
|
||||||
|
InstID = -1
|
||||||
|
|
||||||
|
self.to_music_channels()
|
||||||
|
|
||||||
|
# 此处 我们把通道视为音轨
|
||||||
|
for i in self.channels.keys():
|
||||||
|
# 如果当前通道为空 则跳过
|
||||||
|
if not self.channels[i]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if i == 9:
|
||||||
|
SpecialBits = True
|
||||||
|
else:
|
||||||
|
SpecialBits = False
|
||||||
|
|
||||||
|
nowTrack = []
|
||||||
|
|
||||||
|
for track_no, track in self.channels[i].items():
|
||||||
|
for msg in track:
|
||||||
|
if msg[0] == "PgmC":
|
||||||
|
InstID = msg[1]
|
||||||
|
|
||||||
|
elif msg[0] == "NoteS":
|
||||||
|
soundID, _X = (
|
||||||
|
self.perc_inst_to_soundID_withX(msg[1])
|
||||||
|
if SpecialBits
|
||||||
|
else self.inst_to_souldID_withX(InstID)
|
||||||
|
)
|
||||||
|
score_now = round(msg[-1] / float(speed) / 50)
|
||||||
|
maxScore = max(maxScore, score_now)
|
||||||
|
|
||||||
|
nowTrack.append(
|
||||||
|
self.execute_cmd_head.format(
|
||||||
|
"@a[scores=({}={})]".format(scoreboard_name, score_now)
|
||||||
|
.replace("(", r"{")
|
||||||
|
.replace(")", r"}")
|
||||||
|
)
|
||||||
|
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
|
||||||
|
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
cmdAmount += 1
|
||||||
|
|
||||||
|
if nowTrack:
|
||||||
|
tracks.append(nowTrack)
|
||||||
|
|
||||||
|
return tracks, cmdAmount, maxScore
|
||||||
|
|
||||||
|
def _toCmdList_withDelay_m1(
|
||||||
|
self: MidiConvert,
|
||||||
|
MaxVolume: float = 1.0,
|
||||||
|
speed: float = 1.0,
|
||||||
|
player: str = "@a",
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||||
|
:param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
||||||
|
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||||
|
:param player: 玩家选择器,默认为`@a`
|
||||||
|
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
|
||||||
|
"""
|
||||||
|
tracks = {}
|
||||||
|
|
||||||
|
if speed == 0:
|
||||||
|
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||||
|
if not self.midi:
|
||||||
|
raise MidiUnboundError(
|
||||||
|
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。"
|
||||||
|
)
|
||||||
|
|
||||||
|
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
|
||||||
|
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||||
|
|
||||||
|
for i, track in enumerate(self.midi.tracks):
|
||||||
|
instrumentID = 0
|
||||||
|
ticks = 0
|
||||||
|
|
||||||
|
for msg in track:
|
||||||
|
ticks += msg.time
|
||||||
|
if msg.is_meta:
|
||||||
|
if msg.type == "set_tempo":
|
||||||
|
tempo = msg.tempo
|
||||||
|
else:
|
||||||
|
if msg.type == "program_change":
|
||||||
|
instrumentID = msg.program
|
||||||
|
if msg.type == "note_on" and msg.velocity != 0:
|
||||||
|
now_tick = round(
|
||||||
|
(ticks * tempo)
|
||||||
|
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
|
||||||
|
)
|
||||||
|
soundID, _X = self.inst_to_souldID_withX(instrumentID)
|
||||||
|
try:
|
||||||
|
tracks[now_tick].append(
|
||||||
|
self.execute_cmd_head.format(player)
|
||||||
|
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
|
||||||
|
f"{2 ** ((msg.note - 60 - _X) / 12)}"
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
tracks[now_tick] = [
|
||||||
|
self.execute_cmd_head.format(player)
|
||||||
|
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
|
||||||
|
f"{2 ** ((msg.note - 60 - _X) / 12)}"
|
||||||
|
]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
all_ticks = list(tracks.keys())
|
||||||
|
all_ticks.sort()
|
||||||
|
|
||||||
|
for i in range(len(all_ticks)):
|
||||||
|
if i != 0:
|
||||||
|
for j in range(len(tracks[all_ticks[i]])):
|
||||||
|
if j != 0:
|
||||||
|
results.append((tracks[all_ticks[i]][j], 0))
|
||||||
|
else:
|
||||||
|
results.append(
|
||||||
|
(tracks[all_ticks[i]][j], all_ticks[i] - all_ticks[i - 1])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
for j in range(len(tracks[all_ticks[i]])):
|
||||||
|
results.append((tracks[all_ticks[i]][j], all_ticks[i]))
|
||||||
|
|
||||||
|
return [results, max(all_ticks)]
|
||||||
|
|
||||||
|
def _toCmdList_withDelay_m2(
|
||||||
|
self: MidiConvert,
|
||||||
|
MaxVolume: float = 1.0,
|
||||||
|
speed: float = 1.0,
|
||||||
|
player: str = "@a",
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
使用神羽和金羿的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||||
|
:param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
||||||
|
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||||
|
:param player: 玩家选择器,默认为`@a`
|
||||||
|
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
|
||||||
|
"""
|
||||||
|
tracks = {}
|
||||||
|
if speed == 0:
|
||||||
|
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||||
|
|
||||||
|
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
|
||||||
|
InstID = -1
|
||||||
|
self.to_music_channels()
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for i in self.channels.keys():
|
||||||
|
# 如果当前通道为空 则跳过
|
||||||
|
if not self.channels[i]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if i == 9:
|
||||||
|
SpecialBits = True
|
||||||
|
else:
|
||||||
|
SpecialBits = False
|
||||||
|
|
||||||
|
for track_no, track in self.channels[i].items():
|
||||||
|
for msg in track:
|
||||||
|
if msg[0] == "PgmC":
|
||||||
|
InstID = msg[1]
|
||||||
|
|
||||||
|
elif msg[0] == "NoteS":
|
||||||
|
soundID, _X = (
|
||||||
|
self.perc_inst_to_soundID_withX(msg[1])
|
||||||
|
if SpecialBits
|
||||||
|
else self.inst_to_souldID_withX(InstID)
|
||||||
|
)
|
||||||
|
score_now = round(msg[-1] / float(speed) / 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tracks[score_now].append(
|
||||||
|
self.execute_cmd_head.format(player)
|
||||||
|
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
|
||||||
|
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
tracks[score_now] = [
|
||||||
|
self.execute_cmd_head.format(player)
|
||||||
|
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
|
||||||
|
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
||||||
|
]
|
||||||
|
|
||||||
|
all_ticks = list(tracks.keys())
|
||||||
|
all_ticks.sort()
|
||||||
|
|
||||||
|
for i in range(len(all_ticks)):
|
||||||
|
for j in range(len(tracks[all_ticks[i]])):
|
||||||
|
results.append(
|
||||||
|
(
|
||||||
|
tracks[all_ticks[i]][j],
|
||||||
|
(
|
||||||
|
0
|
||||||
|
if j != 0
|
||||||
|
else (
|
||||||
|
all_ticks[i] - all_ticks[i - 1]
|
||||||
|
if i != 0
|
||||||
|
else all_ticks[i]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return [results, max(all_ticks)]
|
@ -1,463 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
旧版本转换功能以及已经弃用的函数
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
版权所有 © 2023 音·创 开发者
|
|
||||||
Copyright © 2023 all the developers of Musicreater
|
|
||||||
|
|
||||||
开源相关声明请见 仓库根目录下的 License.md
|
|
||||||
Terms & Conditions: License.md in the root directory
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 睿穆组织 开发交流群 861684859
|
|
||||||
# Email TriM-Organization@hotmail.com
|
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
|
||||||
|
|
||||||
|
|
||||||
from ..exceptions import *
|
|
||||||
from ..main import MidiConvert
|
|
||||||
|
|
||||||
|
|
||||||
def to_command_list_method1(
|
|
||||||
self: MidiConvert,
|
|
||||||
scoreboard_name: str = "mscplay",
|
|
||||||
MaxVolume: float = 1.0,
|
|
||||||
speed: float = 1.0,
|
|
||||||
) -> list:
|
|
||||||
"""
|
|
||||||
使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表
|
|
||||||
:param scoreboard_name: 我的世界的计分板名称
|
|
||||||
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
|
||||||
:return: tuple(命令列表, 命令个数, 计分板最大值)
|
|
||||||
"""
|
|
||||||
# :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
|
||||||
tracks = []
|
|
||||||
if speed == 0:
|
|
||||||
if self.debug_mode:
|
|
||||||
raise ZeroSpeedError("播放速度仅可为正实数")
|
|
||||||
speed = 1
|
|
||||||
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
|
|
||||||
|
|
||||||
commands = 0
|
|
||||||
maxscore = 0
|
|
||||||
|
|
||||||
# 分轨的思路其实并不好,但这个算法就是这样
|
|
||||||
# 所以我建议用第二个方法 _toCmdList_m2
|
|
||||||
for i, track in enumerate(self.midi.tracks):
|
|
||||||
ticks = 0
|
|
||||||
instrumentID = 0
|
|
||||||
singleTrack = []
|
|
||||||
|
|
||||||
for msg in track:
|
|
||||||
ticks += msg.time
|
|
||||||
if msg.is_meta:
|
|
||||||
if msg.type == "set_tempo":
|
|
||||||
tempo = msg.tempo
|
|
||||||
else:
|
|
||||||
if msg.type == "program_change":
|
|
||||||
instrumentID = msg.program
|
|
||||||
|
|
||||||
if msg.type == "note_on" and msg.velocity != 0:
|
|
||||||
try:
|
|
||||||
nowscore = round(
|
|
||||||
(ticks * tempo)
|
|
||||||
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
|
|
||||||
)
|
|
||||||
except NameError:
|
|
||||||
raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
|
|
||||||
maxscore = max(maxscore, nowscore)
|
|
||||||
if msg.channel == 9:
|
|
||||||
soundID, _X = self.perc_inst_to_soundID_withX(instrumentID)
|
|
||||||
else:
|
|
||||||
soundID, _X = self.inst_to_souldID_withX(instrumentID)
|
|
||||||
|
|
||||||
singleTrack.append(
|
|
||||||
"execute @a[scores={"
|
|
||||||
+ str(scoreboard_name)
|
|
||||||
+ "="
|
|
||||||
+ str(nowscore)
|
|
||||||
+ "}"
|
|
||||||
+ f"] ~ ~ ~ playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
|
|
||||||
f"{2 ** ((msg.note - 60 - _X) / 12)}"
|
|
||||||
)
|
|
||||||
commands += 1
|
|
||||||
if len(singleTrack) != 0:
|
|
||||||
tracks.append(singleTrack)
|
|
||||||
|
|
||||||
return [tracks, commands, maxscore]
|
|
||||||
|
|
||||||
|
|
||||||
# 原本这个算法的转换效果应该和上面的算法相似的
|
|
||||||
def _toCmdList_m2(
|
|
||||||
self: MidiConvert,
|
|
||||||
scoreboard_name: str = "mscplay",
|
|
||||||
MaxVolume: float = 1.0,
|
|
||||||
speed: float = 1.0,
|
|
||||||
) -> list:
|
|
||||||
"""
|
|
||||||
使用神羽和金羿的转换思路,将midi转换为我的世界命令列表
|
|
||||||
:param scoreboard_name: 我的世界的计分板名称
|
|
||||||
:param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
|
||||||
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
|
||||||
:return: tuple(命令列表, 命令个数, 计分板最大值)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if speed == 0:
|
|
||||||
if self.debug_mode:
|
|
||||||
raise ZeroSpeedError("播放速度仅可为正实数")
|
|
||||||
speed = 1
|
|
||||||
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
|
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
|
||||||
channels = {
|
|
||||||
0: [],
|
|
||||||
1: [],
|
|
||||||
2: [],
|
|
||||||
3: [],
|
|
||||||
4: [],
|
|
||||||
5: [],
|
|
||||||
6: [],
|
|
||||||
7: [],
|
|
||||||
8: [],
|
|
||||||
9: [],
|
|
||||||
10: [],
|
|
||||||
11: [],
|
|
||||||
12: [],
|
|
||||||
13: [],
|
|
||||||
14: [],
|
|
||||||
15: [],
|
|
||||||
16: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
microseconds = 0
|
|
||||||
|
|
||||||
# 我们来用通道统计音乐信息
|
|
||||||
for msg in self.midi:
|
|
||||||
microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样
|
|
||||||
if not msg.is_meta:
|
|
||||||
if self.debug_mode:
|
|
||||||
try:
|
|
||||||
if msg.channel > 15:
|
|
||||||
raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if msg.type == "program_change":
|
|
||||||
channels[msg.channel].append(("PgmC", msg.program, microseconds))
|
|
||||||
|
|
||||||
elif msg.type == "note_on" and msg.velocity != 0:
|
|
||||||
channels[msg.channel].append(
|
|
||||||
("NoteS", msg.note, msg.velocity, microseconds)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif (msg.type == "note_on" and msg.velocity == 0) or (
|
|
||||||
msg.type == "note_off"
|
|
||||||
):
|
|
||||||
channels[msg.channel].append(("NoteE", msg.note, microseconds))
|
|
||||||
|
|
||||||
"""整合后的音乐通道格式
|
|
||||||
每个通道包括若干消息元素其中逃不过这三种:
|
|
||||||
|
|
||||||
1 切换乐器消息
|
|
||||||
("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
|
|
||||||
|
|
||||||
2 音符开始消息
|
|
||||||
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
|
|
||||||
|
|
||||||
3 音符结束消息
|
|
||||||
("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
|
|
||||||
|
|
||||||
tracks = []
|
|
||||||
cmdAmount = 0
|
|
||||||
maxScore = 0
|
|
||||||
|
|
||||||
# 此处 我们把通道视为音轨
|
|
||||||
for i in channels.keys():
|
|
||||||
# 如果当前通道为空 则跳过
|
|
||||||
if not channels[i]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if i == 9:
|
|
||||||
SpecialBits = True
|
|
||||||
else:
|
|
||||||
SpecialBits = False
|
|
||||||
|
|
||||||
nowTrack = []
|
|
||||||
|
|
||||||
for msg in channels[i]:
|
|
||||||
if msg[0] == "PgmC":
|
|
||||||
InstID = msg[1]
|
|
||||||
|
|
||||||
elif msg[0] == "NoteS":
|
|
||||||
try:
|
|
||||||
soundID, _X = (
|
|
||||||
self.perc_inst_to_soundID_withX(InstID)
|
|
||||||
if SpecialBits
|
|
||||||
else self.inst_to_souldID_withX(InstID)
|
|
||||||
)
|
|
||||||
except UnboundLocalError as E:
|
|
||||||
if self.debug_mode:
|
|
||||||
raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
|
|
||||||
else:
|
|
||||||
soundID, _X = (
|
|
||||||
self.perc_inst_to_soundID_withX(-1)
|
|
||||||
if SpecialBits
|
|
||||||
else self.inst_to_souldID_withX(-1)
|
|
||||||
)
|
|
||||||
score_now = round(msg[-1] / float(speed) / 50)
|
|
||||||
maxScore = max(maxScore, score_now)
|
|
||||||
|
|
||||||
nowTrack.append(
|
|
||||||
self.execute_cmd_head.format(
|
|
||||||
"@a[scores=({}={})]".format(scoreboard_name, score_now)
|
|
||||||
.replace("(", r"{")
|
|
||||||
.replace(")", r"}")
|
|
||||||
)
|
|
||||||
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
|
|
||||||
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
cmdAmount += 1
|
|
||||||
|
|
||||||
if nowTrack:
|
|
||||||
tracks.append(nowTrack)
|
|
||||||
|
|
||||||
return [tracks, cmdAmount, maxScore]
|
|
||||||
|
|
||||||
|
|
||||||
def _toCmdList_withDelay_m1(
|
|
||||||
self: MidiConvert,
|
|
||||||
MaxVolume: float = 1.0,
|
|
||||||
speed: float = 1.0,
|
|
||||||
player: str = "@a",
|
|
||||||
) -> list:
|
|
||||||
"""
|
|
||||||
使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
|
||||||
:param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
|
||||||
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
|
||||||
:param player: 玩家选择器,默认为`@a`
|
|
||||||
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
|
|
||||||
"""
|
|
||||||
tracks = {}
|
|
||||||
|
|
||||||
if speed == 0:
|
|
||||||
if self.debug_mode:
|
|
||||||
raise ZeroSpeedError("播放速度仅可为正实数")
|
|
||||||
speed = 1
|
|
||||||
|
|
||||||
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
|
|
||||||
|
|
||||||
for i, track in enumerate(self.midi.tracks):
|
|
||||||
instrumentID = 0
|
|
||||||
ticks = 0
|
|
||||||
|
|
||||||
for msg in track:
|
|
||||||
ticks += msg.time
|
|
||||||
if msg.is_meta:
|
|
||||||
if msg.type == "set_tempo":
|
|
||||||
tempo = msg.tempo
|
|
||||||
else:
|
|
||||||
if msg.type == "program_change":
|
|
||||||
instrumentID = msg.program
|
|
||||||
if msg.type == "note_on" and msg.velocity != 0:
|
|
||||||
now_tick = round(
|
|
||||||
(ticks * tempo)
|
|
||||||
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
|
|
||||||
)
|
|
||||||
soundID, _X = self.inst_to_souldID_withX(instrumentID)
|
|
||||||
try:
|
|
||||||
tracks[now_tick].append(
|
|
||||||
self.execute_cmd_head.format(player)
|
|
||||||
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
|
|
||||||
f"{2 ** ((msg.note - 60 - _X) / 12)}"
|
|
||||||
)
|
|
||||||
except KeyError:
|
|
||||||
tracks[now_tick] = [
|
|
||||||
self.execute_cmd_head.format(player)
|
|
||||||
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
|
|
||||||
f"{2 ** ((msg.note - 60 - _X) / 12)}"
|
|
||||||
]
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
all_ticks = list(tracks.keys())
|
|
||||||
all_ticks.sort()
|
|
||||||
|
|
||||||
for i in range(len(all_ticks)):
|
|
||||||
if i != 0:
|
|
||||||
for j in range(len(tracks[all_ticks[i]])):
|
|
||||||
if j != 0:
|
|
||||||
results.append((tracks[all_ticks[i]][j], 0))
|
|
||||||
else:
|
|
||||||
results.append(
|
|
||||||
(tracks[all_ticks[i]][j], all_ticks[i] - all_ticks[i - 1])
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
for j in range(len(tracks[all_ticks[i]])):
|
|
||||||
results.append((tracks[all_ticks[i]][j], all_ticks[i]))
|
|
||||||
|
|
||||||
return [results, max(all_ticks)]
|
|
||||||
|
|
||||||
|
|
||||||
def _toCmdList_withDelay_m2(
|
|
||||||
self: MidiConvert,
|
|
||||||
MaxVolume: float = 1.0,
|
|
||||||
speed: float = 1.0,
|
|
||||||
player: str = "@a",
|
|
||||||
) -> list:
|
|
||||||
"""
|
|
||||||
使用神羽和金羿的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
|
||||||
:param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
|
||||||
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
|
||||||
:param player: 玩家选择器,默认为`@a`
|
|
||||||
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
|
|
||||||
"""
|
|
||||||
tracks = {}
|
|
||||||
if speed == 0:
|
|
||||||
if self.debug_mode:
|
|
||||||
raise ZeroSpeedError("播放速度仅可为正实数")
|
|
||||||
speed = 1
|
|
||||||
|
|
||||||
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
|
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
|
||||||
channels = {
|
|
||||||
0: [],
|
|
||||||
1: [],
|
|
||||||
2: [],
|
|
||||||
3: [],
|
|
||||||
4: [],
|
|
||||||
5: [],
|
|
||||||
6: [],
|
|
||||||
7: [],
|
|
||||||
8: [],
|
|
||||||
9: [],
|
|
||||||
10: [],
|
|
||||||
11: [],
|
|
||||||
12: [],
|
|
||||||
13: [],
|
|
||||||
14: [],
|
|
||||||
15: [],
|
|
||||||
16: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
microseconds = 0
|
|
||||||
|
|
||||||
# 我们来用通道统计音乐信息
|
|
||||||
for msg in self.midi:
|
|
||||||
try:
|
|
||||||
microseconds += msg.time * 1000 # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样
|
|
||||||
|
|
||||||
# print(microseconds)
|
|
||||||
except NameError:
|
|
||||||
if self.debug_mode:
|
|
||||||
raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
|
|
||||||
else:
|
|
||||||
microseconds += (
|
|
||||||
msg.time * 1000
|
|
||||||
) # 任何人都tm不要动这里,这里循环方式不是track,所以,这里的计时方式不一样
|
|
||||||
|
|
||||||
if msg.is_meta:
|
|
||||||
if msg.type == "set_tempo":
|
|
||||||
tempo = msg.tempo
|
|
||||||
else:
|
|
||||||
if self.debug_mode:
|
|
||||||
try:
|
|
||||||
if msg.channel > 15:
|
|
||||||
raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if msg.type == "program_change":
|
|
||||||
channels[msg.channel].append(("PgmC", msg.program, microseconds))
|
|
||||||
|
|
||||||
elif msg.type == "note_on" and msg.velocity != 0:
|
|
||||||
channels[msg.channel].append(
|
|
||||||
("NoteS", msg.note, msg.velocity, microseconds)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif (msg.type == "note_on" and msg.velocity == 0) or (
|
|
||||||
msg.type == "note_off"
|
|
||||||
):
|
|
||||||
channels[msg.channel].append(("NoteE", msg.note, microseconds))
|
|
||||||
|
|
||||||
"""整合后的音乐通道格式
|
|
||||||
每个通道包括若干消息元素其中逃不过这三种:
|
|
||||||
|
|
||||||
1 切换乐器消息
|
|
||||||
("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
|
|
||||||
|
|
||||||
2 音符开始消息
|
|
||||||
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
|
|
||||||
|
|
||||||
3 音符结束消息
|
|
||||||
("NoteS", 结束的音符ID, 距离演奏开始的毫秒)"""
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
for i in channels.keys():
|
|
||||||
# 如果当前通道为空 则跳过
|
|
||||||
if not channels[i]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if i == 9:
|
|
||||||
SpecialBits = True
|
|
||||||
else:
|
|
||||||
SpecialBits = False
|
|
||||||
|
|
||||||
for msg in channels[i]:
|
|
||||||
if msg[0] == "PgmC":
|
|
||||||
InstID = msg[1]
|
|
||||||
|
|
||||||
elif msg[0] == "NoteS":
|
|
||||||
try:
|
|
||||||
soundID, _X = (
|
|
||||||
self.perc_inst_to_soundID_withX(InstID)
|
|
||||||
if SpecialBits
|
|
||||||
else self.inst_to_souldID_withX(InstID)
|
|
||||||
)
|
|
||||||
except UnboundLocalError as E:
|
|
||||||
if self.debug_mode:
|
|
||||||
raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
|
|
||||||
else:
|
|
||||||
soundID, _X = (
|
|
||||||
self.perc_inst_to_soundID_withX(-1)
|
|
||||||
if SpecialBits
|
|
||||||
else self.inst_to_souldID_withX(-1)
|
|
||||||
)
|
|
||||||
score_now = round(msg[-1] / float(speed) / 50)
|
|
||||||
|
|
||||||
try:
|
|
||||||
tracks[score_now].append(
|
|
||||||
self.execute_cmd_head.format(player)
|
|
||||||
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
|
|
||||||
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
|
||||||
)
|
|
||||||
except KeyError:
|
|
||||||
tracks[score_now] = [
|
|
||||||
self.execute_cmd_head.format(player)
|
|
||||||
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
|
|
||||||
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
|
||||||
]
|
|
||||||
|
|
||||||
all_ticks = list(tracks.keys())
|
|
||||||
all_ticks.sort()
|
|
||||||
|
|
||||||
for i in range(len(all_ticks)):
|
|
||||||
for j in range(len(tracks[all_ticks[i]])):
|
|
||||||
results.append(
|
|
||||||
(
|
|
||||||
tracks[all_ticks[i]][j],
|
|
||||||
(
|
|
||||||
0
|
|
||||||
if j != 0
|
|
||||||
else (
|
|
||||||
all_ticks[i] - all_ticks[i - 1] if i != 0 else all_ticks[i]
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return [results, max(all_ticks)]
|
|
@ -18,9 +18,6 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
T = TypeVar("T") # Declare type variable
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
@ -36,14 +33,17 @@ class SingleNote:
|
|||||||
velocity: int
|
velocity: int
|
||||||
"""力度/响度"""
|
"""力度/响度"""
|
||||||
|
|
||||||
startTime: int
|
start_time: int
|
||||||
"""开始之时 ms"""
|
"""开始之时 ms"""
|
||||||
|
|
||||||
lastTime: int
|
duration: int
|
||||||
"""音符持续时间 ms"""
|
"""音符持续时间 ms"""
|
||||||
|
|
||||||
|
track_no: int
|
||||||
|
"""音符所处的音轨"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, instrument: int, pitch: int, velocity: int, startTime: int, lastTime: int
|
self, instrument: int, pitch: int, velocity: int, startTime: int, lastTime: int, track_number:int = 0
|
||||||
):
|
):
|
||||||
"""用于存储单个音符的类
|
"""用于存储单个音符的类
|
||||||
:param instrument 乐器编号
|
:param instrument 乐器编号
|
||||||
@ -58,10 +58,12 @@ class SingleNote:
|
|||||||
"""音符编号"""
|
"""音符编号"""
|
||||||
self.velocity: int = velocity
|
self.velocity: int = velocity
|
||||||
"""力度/响度"""
|
"""力度/响度"""
|
||||||
self.startTime: int = startTime
|
self.start_time: int = startTime
|
||||||
"""开始之时 ms"""
|
"""开始之时 ms"""
|
||||||
self.lastTime: int = lastTime
|
self.duration: int = lastTime
|
||||||
"""音符持续时间 ms"""
|
"""音符持续时间 ms"""
|
||||||
|
self.track_no: int = track_number
|
||||||
|
"""音符所处的音轨"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inst(self):
|
def inst(self):
|
||||||
@ -80,19 +82,19 @@ class SingleNote:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (
|
return (
|
||||||
f"Note(inst = {self.inst}, pitch = {self.note}, velocity = {self.velocity}, "
|
f"Note(inst = {self.inst}, pitch = {self.note}, velocity = {self.velocity}, "
|
||||||
f"startTime = {self.startTime}, lastTime = {self.lastTime}, )"
|
f"startTime = {self.start_time}, lastTime = {self.duration}, )"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __tuple__(self):
|
def __tuple__(self):
|
||||||
return self.inst, self.note, self.velocity, self.startTime, self.lastTime
|
return self.inst, self.note, self.velocity, self.start_time, self.duration
|
||||||
|
|
||||||
def __dict__(self):
|
def __dict__(self):
|
||||||
return {
|
return {
|
||||||
"inst": self.inst,
|
"inst": self.inst,
|
||||||
"pitch": self.note,
|
"pitch": self.note,
|
||||||
"velocity": self.velocity,
|
"velocity": self.velocity,
|
||||||
"startTime": self.startTime,
|
"startTime": self.start_time,
|
||||||
"lastTime": self.lastTime,
|
"lastTime": self.duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -168,18 +170,3 @@ class SingleCommand:
|
|||||||
if not isinstance(other, self.__class__):
|
if not isinstance(other, self.__class__):
|
||||||
return False
|
return False
|
||||||
return self.__str__() == other.__str__()
|
return self.__str__() == other.__str__()
|
||||||
|
|
||||||
|
|
||||||
class MethodList(list):
|
|
||||||
"""函数列表,列表中的所有元素均为函数"""
|
|
||||||
|
|
||||||
def __init__(self, in_=()):
|
|
||||||
"""函数列表,列表中的所有元素均为函数"""
|
|
||||||
super().__init__()
|
|
||||||
self._T = [_x for _x in in_]
|
|
||||||
|
|
||||||
def __getitem__(self, item) -> T:
|
|
||||||
return self._T[item]
|
|
||||||
|
|
||||||
def __len__(self) -> int:
|
|
||||||
return self._T.__len__()
|
|
||||||
|
@ -15,7 +15,8 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
from typing import Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
|
||||||
def mctick2timestr(mc_tick: int) -> str:
|
def mctick2timestr(mc_tick: int) -> str:
|
||||||
"""
|
"""
|
||||||
@ -24,8 +25,15 @@ def mctick2timestr(mc_tick: int) -> str:
|
|||||||
return str(int(int(mc_tick / 20) / 60)) + ":" + str(int(int(mc_tick / 20) % 60))
|
return str(int(int(mc_tick / 20) / 60)) + ":" + str(int(int(mc_tick / 20) % 60))
|
||||||
|
|
||||||
|
|
||||||
def empty_midi_channels(channel_count: int = 17) -> Dict[int, Dict]:
|
def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]:
|
||||||
"""
|
"""
|
||||||
空MIDI通道字典
|
空MIDI通道字典
|
||||||
"""
|
"""
|
||||||
return dict((i, {}) for i in range(channel_count))
|
|
||||||
|
return dict(
|
||||||
|
(
|
||||||
|
i,
|
||||||
|
(staff.copy() if isinstance(staff, (dict, list)) else staff),
|
||||||
|
) # 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
||||||
|
for i in range(channel_count)
|
||||||
|
)
|
||||||
|
@ -203,7 +203,7 @@ print(convertion_result)
|
|||||||
在**音·创**中,用来达到这种效果的指令是这样的:
|
在**音·创**中,用来达到这种效果的指令是这样的:
|
||||||
|
|
||||||
```mcfunction
|
```mcfunction
|
||||||
execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ~ ~Ht ~ Vlct Ptc
|
execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
|
||||||
```
|
```
|
||||||
|
|
||||||
|参数|说明|备注|
|
|参数|说明|备注|
|
||||||
@ -224,7 +224,7 @@ print(convertion_result)
|
|||||||
在音·创中,由于此方式播放的音乐不需要用计分板,所以播放指令是这样的:
|
在音·创中,由于此方式播放的音乐不需要用计分板,所以播放指令是这样的:
|
||||||
|
|
||||||
```mcfunction
|
```mcfunction
|
||||||
execute Tg ~ ~ ~ playsound InstID @s ~ ~Ht ~ Vlct Ptc
|
execute Tg ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
|
||||||
```
|
```
|
||||||
|
|
||||||
|参数|说明|备注|
|
|参数|说明|备注|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
*由于先前的 **读我文件**(README.md) 过于冗杂,现另辟蹊径来给大家全方位的教程。*
|
*由于先前的 **读我文件**(README.md) 过于冗杂,现另辟蹊径来给大家全方位的教程。*
|
||||||
|
|
||||||
*这是本库所生成文件的使用声明,不是使用本库的教程,若要查看**本库的演示程序**使用教程,可点击[此处](%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md);若要查看有关文件结构的内容,可以点击[此处](./%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E.md)*
|
*这是本库所生成文件的使用声明,不是使用本库的教程,若要查看**本库的大致文档**,可点击[此处](./%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md);若要查看有关文件结构的内容,可以点击[此处](./%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E.md)*
|
||||||
|
|
||||||
## 附加包格式
|
## 附加包格式
|
||||||
|
|
||||||
|
13
example_futureFunction.py
Normal file
13
example_futureFunction.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import Musicreater.experiment
|
||||||
|
import Musicreater.plugin
|
||||||
|
import Musicreater.plugin.mcstructpack
|
||||||
|
|
||||||
|
print(
|
||||||
|
Musicreater.plugin.mcstructpack.to_mcstructure_addon_in_delay(
|
||||||
|
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(input("midi路径:"), old_exe_format=False),
|
||||||
|
Musicreater.plugin.ConvertConfig(
|
||||||
|
input("输出路径:"),
|
||||||
|
volume=1
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
2
setup.py
2
setup.py
@ -5,7 +5,7 @@ import Musicreater
|
|||||||
with open("requirements.txt", "r", encoding="utf-8") as fh:
|
with open("requirements.txt", "r", encoding="utf-8") as fh:
|
||||||
dependences = fh.read().strip().split("\n")
|
dependences = fh.read().strip().split("\n")
|
||||||
|
|
||||||
with open("README.md", "r", encoding="utf-8") as fh:
|
with open("README_EN.md", "r", encoding="utf-8") as fh:
|
||||||
long_description = fh.read().replace(
|
long_description = fh.read().replace(
|
||||||
"./docs/", "https://github.com/TriM-Organization/Musicreater/blob/master/docs/"
|
"./docs/", "https://github.com/TriM-Organization/Musicreater/blob/master/docs/"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user