新增第三种算法,解决通道与音轨的乐器问题;修复to_DICT中的音符时间问题

This commit is contained in:
EillesWan 2023-02-05 16:16:12 +08:00
parent c419adc274
commit 26fbd346d8
2 changed files with 350 additions and 48 deletions

View File

@ -19,7 +19,7 @@ Terms & Conditions: ../License.md
from .main import * from .main import *
__version__ = "0.2.3" __version__ = "0.3"
__all__ = [] __all__ = []
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray")) __author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"))

View File

@ -148,7 +148,7 @@ class midiConvert:
self.midFileName: str = "" self.midFileName: str = ""
self.exeHead = "" self.exeHead = ""
self.methods = MethodList( self.methods = MethodList(
[self._toCmdList_m1, self._toCmdList_m2, self._toCmdList_m3] [self._toCmdList_m1, self._toCmdList_m2, self._toCmdList_m3, self._toCmdList_m4]
) )
self.methods_byDelay = MethodList( self.methods_byDelay = MethodList(
@ -472,7 +472,7 @@ class midiConvert:
speed: float = 1.0, speed: float = 1.0,
) -> list: ) -> list:
""" """
使用金羿的转换思路将midi转换为我的世界命令列表 使用神羽和金羿的转换思路将midi转换为我的世界命令列表
:param scoreboard_name: 我的世界的计分板名称 :param scoreboard_name: 我的世界的计分板名称
:param MaxVolume: 音量注意这里的音量范围为(0,1]如果超出将被处理为正确值其原理为在距离玩家 (1 / volume -1) 的地方播放音频 :param MaxVolume: 音量注意这里的音量范围为(0,1]如果超出将被处理为正确值其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed :param speed: 速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed
@ -510,22 +510,22 @@ class midiConvert:
# 我们来用通道统计音乐信息 # 我们来用通道统计音乐信息
for msg in self.midi: for msg in self.midi:
try: # try:
microseconds += msg.time * 1000 # 任何人都tm不要动这里这里循环方式不是track所以这里的计时方式不一样 microseconds += msg.time * 1000 # 任何人都tm不要动这里这里循环方式不是track所以这里的计时方式不一样
# print(microseconds) # print(microseconds)
except NameError: # except NameError:
if self.debugMode: # if self.debugMode:
raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo") # raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
else: # else:
microseconds += ( # microseconds += (
msg.time * 1000 # 任何人都tm不要动这里这里循环方式不是track所以这里的计时方式不一样 # msg.time * 1000 # 任何人都tm不要动这里这里循环方式不是track所以这里的计时方式不一样
) # )
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
else:
# if msg.is_meta:
# if msg.type == "set_tempo":
# tempo = msg.tempo
# else:
if not msg.is_meta:
if self.debugMode: if self.debugMode:
try: try:
if msg.channel > 15: if msg.channel > 15:
@ -616,12 +616,153 @@ class midiConvert:
return [tracks, cmdAmount, maxScore] return [tracks, cmdAmount, maxScore]
# 简单的单音填充
def _toCmdList_m3( def _toCmdList_m3(
self, self,
scoreboard_name: str = "mscplay", scoreboard_name: str = "mscplay",
MaxVolume: float = 1.0, MaxVolume: float = 1.0,
speed: 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.debugMode:
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: {}}
# 我们来用通道统计音乐信息
# 但是是用分轨的思路的
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
# print(microseconds)
except NameError:
if self.debugMode:
raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
else:
microseconds += (msg.time * mido.midifiles.midifiles.DEFAULT_TEMPO / self.midi.ticks_per_beat)
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
if self.debugMode:
self.prt(f"TEMPO更改{tempo}(毫秒每拍)")
else:
if self.debugMode:
try:
if msg.channel > 15:
raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
except AttributeError:
pass
if not track_no in channels[msg.channel].keys():
channels[msg.channel][track_no] = []
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, 距离演奏开始的毫秒)"""
tracks = []
cmdAmount = 0
maxScore = 0
# 此处 我们把通道视为音轨
for i in channels.keys():
# 如果当前通道为空 则跳过
if not channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
# nowChannel = []
for track_no,track in channels[i].items():
nowTrack = []
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
try:
soundID, _X = (
self.__bitInst2ID_withX(InstID)
if SpecialBits
else self.__Inst2soundID_withX(InstID)
)
except UnboundLocalError as E:
if self.debugMode:
raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
else:
soundID, _X = (
self.__bitInst2ID_withX(-1)
if SpecialBits
else self.__Inst2soundID_withX(-1)
)
score_now = round(msg[-1] / float(speed) / 50)
maxScore = max(maxScore, score_now)
nowTrack.append(
self.exeHead.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_m4(
self,
scoreboard_name: str = "mscplay",
MaxVolume: float = 1.0,
speed: float = 1.0,
) -> list: ) -> list:
""" """
使用金羿的转换思路将midi转换为我的世界命令列表并使用完全填充算法优化音感 使用金羿的转换思路将midi转换为我的世界命令列表并使用完全填充算法优化音感
@ -811,11 +952,11 @@ class midiConvert:
) -> list: ) -> list:
""" """
使用Dislink Sforza的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟 使用Dislink Sforza的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟
:param MaxVolume: 最大播放音量注意这里的音量范围为(0,1]如果超出将被处理为正确值其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed :param speed: 速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed
:param player: 玩家选择器默认为`@a` :param player: 玩家选择器默认为`@a`
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...] :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
""" """
# :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
tracks = {} tracks = {}
if speed == 0: if speed == 0:
@ -883,12 +1024,12 @@ class midiConvert:
player: str = "@a", player: str = "@a",
) -> list: ) -> list:
""" """
使用金羿的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟 使用神羽和金羿的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟
:param MaxVolume: 最大播放音量注意这里的音量范围为(0,1]如果超出将被处理为正确值其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed :param speed: 速度注意这里的速度指的是播放倍率其原理为在播放音频的时候每个音符的播放时间除以 speed
:param player: 玩家选择器默认为`@a` :param player: 玩家选择器默认为`@a`
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...] :return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
""" """
# :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
tracks = {} tracks = {}
if speed == 0: if speed == 0:
if self.debugMode: if self.debugMode:
@ -1038,6 +1179,153 @@ class midiConvert:
return [results, max(all_ticks)] return [results, max(all_ticks)]
def _toCmdList_withDelay_m3(
self,
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距离上一个指令的延迟 ),...]
"""
if speed == 0:
if self.debugMode:
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: {}}
# 我们来用通道统计音乐信息
# 但是是用分轨的思路的
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
# print(microseconds)
except NameError:
if self.debugMode:
raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
else:
microseconds += (msg.time * mido.midifiles.midifiles.DEFAULT_TEMPO / self.midi.ticks_per_beat)
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
if self.debugMode:
self.prt(f"TEMPO更改{tempo}(毫秒每拍)")
else:
if self.debugMode:
try:
if msg.channel > 15:
raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
except AttributeError:
pass
if not track_no in channels[msg.channel].keys():
channels[msg.channel][track_no] = []
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, 距离演奏开始的毫秒)"""
tracks = {}
# 此处 我们把通道视为音轨
for i in channels.keys():
# 如果当前通道为空 则跳过
if not channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
# nowChannel = []
for track_no,track in channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
try:
soundID, _X = (
self.__bitInst2ID_withX(InstID)
if SpecialBits
else self.__Inst2soundID_withX(InstID)
)
except UnboundLocalError as E:
if self.debugMode:
raise NotDefineProgramError(f"未定义乐器便提前演奏。\n{E}")
else:
soundID, _X = (
self.__bitInst2ID_withX(-1)
if SpecialBits
else self.__Inst2soundID_withX(-1)
)
score_now = round(msg[-1] / float(speed) / 50)
try:
tracks[score_now].append(
self.exeHead.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.exeHead.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
]
all_ticks = list(tracks.keys())
results = []
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)]
def to_mcpack( def to_mcpack(
self, self,
method: int = 1, method: int = 1,
@ -1384,40 +1672,54 @@ class midiConvert:
""" """
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨 # 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
channels = {} 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: # 但是是用分轨的思路的
for track_no, track in enumerate(self.midi.tracks):
microseconds = 0
for msg in track:
if msg.time != 0: if msg.time != 0:
try: try:
microseconds += msg.time * tempo / self.midi.ticks_per_beat microseconds += msg.time * tempo / self.midi.ticks_per_beat
# print(microseconds) # print(microseconds)
except NameError: except NameError:
microseconds += ( if self.debugMode:
msg.time raise NotDefineTempoError("计算当前分数时出错 未定义参量 Tempo")
* mido.midifiles.midifiles.DEFAULT_TEMPO else:
/ self.midi.ticks_per_beat microseconds += (msg.time * mido.midifiles.midifiles.DEFAULT_TEMPO / self.midi.ticks_per_beat)
)
if msg.is_meta: if msg.is_meta:
if msg.type == "set_tempo": if msg.type == "set_tempo":
tempo = msg.tempo tempo = msg.tempo
if self.debugMode:
self.prt(f"TEMPO更改{tempo}(毫秒每拍)")
else: else:
if self.debugMode:
try:
if msg.channel > 15:
raise ChannelOverFlowError(f"当前消息 {msg} 的通道超限(≤15)")
except AttributeError:
pass
if not track_no in channels[msg.channel].keys():
channels[msg.channel][track_no] = []
if msg.type == "program_change": if msg.type == "program_change":
channels[msg.channel].append(("PgmC", msg.program, microseconds)) channels[msg.channel][track_no].append(("PgmC", msg.program, microseconds))
elif msg.type == "note_on" and msg.velocity != 0: elif msg.type == "note_on" and msg.velocity != 0:
channels[msg.channel].append( channels[msg.channel][track_no].append(
("NoteS", msg.note, msg.velocity, microseconds) ("NoteS", msg.note, msg.velocity, microseconds)
) )
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"
): ):
channels[msg.channel].append(("NoteE", msg.note, microseconds)) channels[msg.channel][track_no].append(("NoteE", msg.note, microseconds))
"""整合后的音乐通道格式 """整合后的音乐通道格式
每个通道包括若干消息元素其中逃不过这三种 每个通道包括若干消息元素其中逃不过这三种