2022-08-08 22:08:08 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 音·创 开发交流群 861684859
|
|
|
|
|
# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
|
|
|
|
|
# 若需使用或借鉴 请依照 Apache 2.0 许可证进行许可
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
音·创 库版 (Musicreater Package Version)
|
|
|
|
|
是一款免费开源的针对《我的世界:基岩版》的midi音乐转换库
|
|
|
|
|
注意!除了此源文件以外,任何属于此仓库以及此项目的文件均依照Apache许可证进行许可
|
|
|
|
|
Musicreater pkgver (Package Version 音·创 库版)
|
|
|
|
|
A free open source library used for convert midi file into formats that is suitable for **Minecraft: Bedrock Edition**.
|
|
|
|
|
Note! Except for this source file, all the files in this repository and this project are licensed under Apache License 2.0
|
|
|
|
|
|
|
|
|
|
Copyright 2022 all the developers of Musicreater
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the 'License');
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an 'AS IS' BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _toCmdList_m1(
|
2022-10-05 22:18:03 +08:00
|
|
|
|
self,
|
|
|
|
|
scoreboardname: str = "mscplay",
|
|
|
|
|
volume: float = 1.0,
|
|
|
|
|
speed: float = 1.0) -> list:
|
2022-08-08 22:08:08 +08:00
|
|
|
|
"""
|
|
|
|
|
使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表
|
|
|
|
|
:param scoreboardname: 我的世界的计分板名称
|
|
|
|
|
:param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
|
|
|
|
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
|
|
|
|
:return: tuple(命令列表, 命令个数, 计分板最大值)
|
|
|
|
|
"""
|
|
|
|
|
tracks = []
|
|
|
|
|
if volume > 1:
|
|
|
|
|
volume = 1
|
|
|
|
|
if volume <= 0:
|
|
|
|
|
volume = 0.001
|
|
|
|
|
|
|
|
|
|
commands = 0
|
|
|
|
|
maxscore = 0
|
|
|
|
|
|
|
|
|
|
for i, track in enumerate(self.midi.tracks):
|
|
|
|
|
|
|
|
|
|
ticks = 0
|
|
|
|
|
instrumentID = 0
|
|
|
|
|
singleTrack = []
|
|
|
|
|
|
|
|
|
|
for msg in track:
|
|
|
|
|
ticks += msg.time
|
|
|
|
|
# print(msg)
|
|
|
|
|
if msg.is_meta:
|
|
|
|
|
if msg.type == "set_tempo":
|
|
|
|
|
tempo = msg.tempo
|
|
|
|
|
else:
|
|
|
|
|
if msg.type == "program_change":
|
|
|
|
|
# print("TT")
|
|
|
|
|
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)
|
|
|
|
|
soundID, _X = self.__Inst2soundIDwithX(instrumentID)
|
|
|
|
|
singleTrack.append(
|
2022-10-05 22:18:03 +08:00
|
|
|
|
"execute @a[scores={" +
|
|
|
|
|
str(scoreboardname) +
|
|
|
|
|
"=" +
|
|
|
|
|
str(nowscore) +
|
|
|
|
|
"}" +
|
|
|
|
|
f"] ~ ~ ~ playsound {soundID} @s ~ ~{1 / volume - 1} ~ {msg.velocity * (0.7 if msg.channel == 0 else 0.9)} {2 ** ((msg.note - 60 - _X) / 12)}")
|
2022-08-08 22:08:08 +08:00
|
|
|
|
commands += 1
|
|
|
|
|
if len(singleTrack) != 0:
|
|
|
|
|
tracks.append(singleTrack)
|
|
|
|
|
|
2022-10-05 22:18:03 +08:00
|
|
|
|
return [tracks, commands, maxscore]
|
2022-10-06 00:06:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import mido
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def delete_extra_zero(n: float) -> int or float:
|
|
|
|
|
"""
|
|
|
|
|
删除多余的0
|
|
|
|
|
————————————————
|
|
|
|
|
版权声明:本文为CSDN博主「XerCis」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
|
|
|
|
|
原文链接:https://blog.csdn.net/lly1122334/article/details/108770141
|
|
|
|
|
删除小数点后多余的0
|
|
|
|
|
:param n: input
|
|
|
|
|
:return: output
|
|
|
|
|
"""
|
|
|
|
|
n = '{:g}'.format(n)
|
|
|
|
|
n = float(n) if '.' in n else int(n) # 含小数点转float否则int
|
|
|
|
|
return n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bpm_by_MetaMessage_Set_tempo(tmp: int) -> int or float:
|
|
|
|
|
"""
|
|
|
|
|
midi文件tempo事件bpm算法。
|
|
|
|
|
A function that's used to compute the bpm of a midiFile,
|
|
|
|
|
which algorithm is made up of midiFile's tempo meta message.
|
|
|
|
|
:param tmp:输入mid的metaMessage中速度tempo值
|
|
|
|
|
input the tempo value which is in the tempo meta message.
|
|
|
|
|
:return:bpm
|
|
|
|
|
|
|
|
|
|
This algorithm is made by ©bgArray.
|
|
|
|
|
算法版权归©诸葛亮与八卦阵所有。
|
|
|
|
|
"""
|
|
|
|
|
second = tmp / 1000000
|
|
|
|
|
bpm = delete_extra_zero(60 / second)
|
|
|
|
|
# debug.dp(bpm)
|
|
|
|
|
return bpm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NoteMessage:
|
|
|
|
|
def __init__(self, channel, pitch, velocity, startT, lastT, midi, now_bpm, change_bpm=None):
|
|
|
|
|
self.channel = channel
|
|
|
|
|
self.note = pitch
|
|
|
|
|
self.velocity = velocity
|
|
|
|
|
self.startTime = startT
|
|
|
|
|
self.lastTime = lastT
|
|
|
|
|
self.tempo = now_bpm # 这里要程序实现获取bpm可以参考我的程序
|
|
|
|
|
|
|
|
|
|
def mt2gt(mt, tpb_a, bpm_a):
|
|
|
|
|
return mt / tpb_a / bpm_a * 60
|
|
|
|
|
self.startTrueTime = mt2gt(self.startTime, midi.ticks_per_beat, self.tempo) # / 20
|
|
|
|
|
# delete_extra_zero(round_up())
|
|
|
|
|
if change_bpm is not None:
|
|
|
|
|
self.lastTrueTime = mt2gt(self.lastTime, midi.ticks_per_beat, change_bpm) # / 20
|
|
|
|
|
else:
|
|
|
|
|
self.lastTrueTime = mt2gt(self.lastTime, midi.ticks_per_beat, self.tempo) # / 20
|
|
|
|
|
# delete_extra_zero(round_up())
|
|
|
|
|
print((self.startTime * self.tempo) / (midi.ticks_per_beat * 50000))
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return "noteMessage channel=" + str(self.channel) + " note=" + str(self.note) + " velocity=" + \
|
|
|
|
|
str(self.velocity) + " startTime=" + str(self.startTime) + " lastTime=" + str(self.lastTime) + \
|
|
|
|
|
" startTrueTime=" + str(self.startTrueTime) + " lastTrueTime=" + str(self.lastTrueTime)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load(mid: mido.MidiFile):
|
|
|
|
|
|
|
|
|
|
type_ = [False, False, False] # note_off / note_on+0 / mixed
|
|
|
|
|
|
|
|
|
|
is_tempo = False
|
|
|
|
|
|
|
|
|
|
# 预检
|
|
|
|
|
for i, track in enumerate(mid.tracks):
|
|
|
|
|
for msg in track:
|
|
|
|
|
# print(msg)
|
|
|
|
|
if msg.is_meta is not True:
|
|
|
|
|
if msg.type == 'note_on' and msg.velocity == 0:
|
|
|
|
|
type_[1] = True
|
|
|
|
|
elif msg.type == "note_off":
|
|
|
|
|
type_[0] = True
|
|
|
|
|
if msg.is_meta is True and msg.type == "set_tempo":
|
|
|
|
|
is_tempo = True
|
|
|
|
|
|
|
|
|
|
if is_tempo is not True:
|
|
|
|
|
raise Exception("这个mid没有可供计算时间的tempo事件")
|
|
|
|
|
|
|
|
|
|
if type_[0] is True and type_[1] is True:
|
|
|
|
|
type_[2] = True
|
|
|
|
|
type_[1] = False
|
|
|
|
|
type_[0] = False
|
|
|
|
|
print(type_)
|
|
|
|
|
|
|
|
|
|
bpm = 0
|
|
|
|
|
recent_change_bpm = 0
|
|
|
|
|
is_change_bpm = False
|
|
|
|
|
# 实检
|
|
|
|
|
for i, track in enumerate(mid.tracks):
|
|
|
|
|
noteOn = []
|
|
|
|
|
trackS = []
|
|
|
|
|
ticks = 0
|
|
|
|
|
for msg in track:
|
|
|
|
|
print(msg)
|
|
|
|
|
ticks += msg.time
|
|
|
|
|
print(ticks)
|
|
|
|
|
if msg.is_meta is True and msg.type == "set_tempo":
|
|
|
|
|
recent_change_bpm = bpm
|
|
|
|
|
bpm = bpm_by_MetaMessage_Set_tempo(msg.tempo)
|
|
|
|
|
is_change_bpm = True
|
|
|
|
|
# print((ticks * 92) / (mid.ticks_per_beat * 50000))
|
|
|
|
|
# MC_tick = delete_extra_zero(round_up(
|
|
|
|
|
# (ticks * 92) / (mid.ticks_per_beat * 50000)
|
|
|
|
|
# ))
|
|
|
|
|
# print(MC_tick)
|
|
|
|
|
# print(ticks / mid.ticks_per_beat / 92 * 60)
|
|
|
|
|
if msg.type == 'note_on' and msg.velocity != 0:
|
|
|
|
|
noteOn.append([msg, msg.note, ticks])
|
|
|
|
|
if type_[1] is True:
|
|
|
|
|
if msg.type == 'note_on' and msg.velocity == 0:
|
|
|
|
|
for u in noteOn:
|
|
|
|
|
index = 0
|
|
|
|
|
if u[1] == msg.note:
|
|
|
|
|
lastMessage = u[0]
|
|
|
|
|
lastTick = u[2]
|
|
|
|
|
break
|
|
|
|
|
index += 1
|
|
|
|
|
print(lastTick)
|
|
|
|
|
if is_change_bpm and recent_change_bpm != 0:
|
|
|
|
|
trackS.append(NoteMessage(msg.channel, msg.note, lastMessage.velocity, lastTick, ticks - lastTick,
|
|
|
|
|
mid, recent_change_bpm, bpm))
|
|
|
|
|
is_change_bpm = False
|
|
|
|
|
else:
|
|
|
|
|
trackS.append(
|
|
|
|
|
NoteMessage(msg.channel, msg.note, lastMessage.velocity, lastTick, ticks - lastTick,
|
|
|
|
|
mid, bpm))
|
|
|
|
|
# print(noteOn)
|
|
|
|
|
# print(index)
|
|
|
|
|
try:
|
|
|
|
|
noteOn.pop(index)
|
|
|
|
|
except IndexError:
|
|
|
|
|
noteOn.pop(index - 1)
|
|
|
|
|
print(trackS)
|
|
|
|
|
for j in trackS:
|
|
|
|
|
print(j)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
load(mido.MidiFile("test.mid"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|