重大更新:优化算法+红石指令音乐生成

This commit is contained in:
EillesWan 2023-08-12 12:39:30 +08:00
parent 5c86a28b44
commit 83c9750db3
20 changed files with 689 additions and 163 deletions

View File

@ -17,8 +17,8 @@ Terms & Conditions: License.md in the root directory
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__version__ = "1.2.1" __version__ = "1.4.0"
__vername__ = "扩大对有问题的Midi文件的兼容性" __vername__ = "红石指令音乐的生成"
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon")) __author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon"))
__all__ = [ __all__ = [
# 主要类 # 主要类

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import Dict, List, Tuple
""" """
存放常量与数值性内容 存放常量与数值性内容
""" """
@ -40,30 +42,34 @@ DEFAULT_PROGRESSBAR_STYLE = (
默认的进度条样式组 默认的进度条样式组
""" """
PITCHED_INSTRUMENT_LIST = {
# 以下是由 Touch “偷吃” 带来的高准确率音效对照表
# 包括乐音乐器对照和打击乐器对照
PITCHED_INSTRUMENT_TABLE: Dict[int, Tuple[str, int]] = {
0: ("note.harp", 6), 0: ("note.harp", 6),
1: ("note.harp", 6), 1: ("note.harp", 6),
2: ("note.pling", 6), 2: ("note.pling", 6),
3: ("note.harp", 6), 3: ("note.harp", 6),
4: ("note.pling", 6), 4: ("note.pling", 6),
5: ("note.pling", 6), 5: ("note.pling", 6),
6: ("note.harp", 6), 6: ("note.guitar", 7),
7: ("note.harp", 6), 7: ("note.harp", 6),
8: ("note.share", 7), # 打击乐器无音域 8: ("note.bell", 4), # 打击乐器无音域
9: ("note.harp", 6), 9: ("note.bell", 4),
10: ("note.didgeridoo", 8), 10: ("note.iron_xylophone", 6),
11: ("note.harp", 6), 11: ("note.iron_xylophone", 6),
12: ("note.xylophone", 4), 12: ("note.iron_xylophone", 6),
13: ("note.chime", 4), 13: ("note.xylophone", 4),
14: ("note.harp", 6), 14: ("note.chime", 4),
15: ("note.harp", 6), 15: ("note.harp", 6),
16: ("note.bass", 8), 16: ("note.bass", 8),
17: ("note.harp", 6), 17: ("note.harp", 6),
18: ("note.harp", 6), 18: ("note.flute", 5),
19: ("note.harp", 6), 19: ("note.harp", 6),
20: ("note.harp", 6), 20: ("note.harp", 6),
21: ("note.harp", 6), 21: ("note.flute", 5),
22: ("note.harp", 6), 22: ("note.flute", 5),
23: ("note.guitar", 7), 23: ("note.guitar", 7),
24: ("note.guitar", 7), 24: ("note.guitar", 7),
25: ("note.guitar", 7), 25: ("note.guitar", 7),
@ -81,21 +87,21 @@ PITCHED_INSTRUMENT_LIST = {
37: ("note.bass", 8), 37: ("note.bass", 8),
38: ("note.bass", 8), 38: ("note.bass", 8),
39: ("note.bass", 8), 39: ("note.bass", 8),
40: ("note.harp", 6), 40: ("note.flute", 5),
41: ("note.harp", 6), 41: ("note.flute", 5),
42: ("note.harp", 6), 42: ("note.flute", 5),
43: ("note.harp", 6), 43: ("note.flute", 5),
44: ("note.iron_xylophone", 6), 44: ("note.iron_xylophone", 6),
45: ("note.guitar", 7), 45: ("note.guitar", 7),
46: ("note.harp", 6), 46: ("note.harp", 6),
47: ("note.harp", 6), 47: ("note.bd", 7),
48: ("note.guitar", 7), 48: ("note.guitar", 7),
49: ("note.guitar", 7), 49: ("note.guitar", 7),
50: ("note.bit", 6), 50: ("note.bit", 6),
51: ("note.bit", 6), 51: ("note.bit", 6),
52: ("note.harp", 6), 52: ("note.flute", 5),
53: ("note.harp", 6), 53: ("note.flute", 5),
54: ("note.bit", 6), 54: ("note.flute", 5),
55: ("note.flute", 5), 55: ("note.flute", 5),
56: ("note.flute", 5), 56: ("note.flute", 5),
57: ("note.flute", 5), 57: ("note.flute", 5),
@ -110,16 +116,16 @@ PITCHED_INSTRUMENT_LIST = {
66: ("note.bit", 6), 66: ("note.bit", 6),
67: ("note.bit", 6), 67: ("note.bit", 6),
68: ("note.flute", 5), 68: ("note.flute", 5),
69: ("note.harp", 6), 69: ("note.bit", 6),
70: ("note.harp", 6), 70: ("note.banjo", 6),
71: ("note.flute", 5), 71: ("note.flute", 5),
72: ("note.flute", 5), 72: ("note.flute", 5),
73: ("note.flute", 5), 73: ("note.flute", 5),
74: ("note.harp", 6), 74: ("note.flute", 5),
75: ("note.flute", 5), 75: ("note.flute", 5),
76: ("note.harp", 6), 76: ("note.harp", 6),
77: ("note.harp", 6), 77: ("note.harp", 6),
78: ("note.harp", 6), 78: ("note.flute", 5),
79: ("note.harp", 6), 79: ("note.harp", 6),
80: ("note.bit", 6), 80: ("note.bit", 6),
81: ("note.bit", 6), 81: ("note.bit", 6),
@ -149,35 +155,35 @@ PITCHED_INSTRUMENT_LIST = {
105: ("note.banjo", 6), 105: ("note.banjo", 6),
106: ("note.harp", 6), 106: ("note.harp", 6),
107: ("note.harp", 6), 107: ("note.harp", 6),
108: ("note.harp", 6), 108: ("note.bell", 4),
109: ("note.harp", 6), 109: ("note.flute", 5),
110: ("note.harp", 6), 110: ("note.flute", 5),
111: ("note.guitar", 7), 111: ("note.guitar", 7),
112: ("note.harp", 6), 112: ("note.bell", 4),
113: ("note.bell", 4), 113: ("note.bell", 4),
114: ("note.harp", 6), 114: ("note.flute", 5),
115: ("note.cow_bell", 5), 115: ("note.cow_bell", 5),
116: ("note.bd", 7), # 打击乐器无音域 116: ("note.bd", 7), # 打击乐器无音域
117: ("note.bass", 8), 117: ("note.bass", 8),
118: ("note.bit", 6), 118: ("note.bit", 6),
119: ("note.bd", 7), # 打击乐器无音域 119: ("firework.blast", 7), # 打击乐器无音域
120: ("note.guitar", 7), 120: ("note.guitar", 7),
121: ("note.harp", 6), 121: ("note.harp", 6),
122: ("note.harp", 6), 122: ("note.harp", 6),
123: ("note.harp", 6), 123: ("note.harp", 6),
124: ("note.harp", 6), 124: ("note.harp", 6),
125: ("note.hat", 7), # 打击乐器无音域 125: ("note.hat", 7), # 打击乐器无音域
126: ("note.bd", 7), # 打击乐器无音域 126: ("firework.twinkle", 7), # 打击乐器无音域
127: ("note.snare", 7), # 打击乐器无音域 127: ("note.snare", 7), # 打击乐器无音域
} }
PERCUSSION_INSTRUMENT_LIST = { PERCUSSION_INSTRUMENT_TABLE: Dict[int, Tuple[str, int]] = {
34: ("note.bd", 7), 34: ("note.bd", 7),
35: ("note.bd", 7), 35: ("note.bd", 7),
36: ("note.hat", 7), 36: ("note.hat", 7),
37: ("note.snare", 7), 37: ("note.snare", 7),
38: ("note.snare", 7), 38: ("note.snare", 7),
39: ("note.snare", 7), 39: ("fire.ignite", 7),
40: ("note.hat", 7), 40: ("note.hat", 7),
41: ("note.snare", 7), 41: ("note.snare", 7),
42: ("note.hat", 7), 42: ("note.hat", 7),
@ -221,11 +227,22 @@ PERCUSSION_INSTRUMENT_LIST = {
80: ("note.bell", 4), 80: ("note.bell", 4),
} }
INSTRUMENT_BLOCKS_LIST = { PERCUSSION_INSTRUMENT_LIST: List[str] = [
"note.snare",
"note.bd",
"note.hat",
"note.basedrum",
"firework.blast",
"firework.twinkle",
"fire.ignite",
]
INSTRUMENT_BLOCKS_TABLE: Dict[str, Tuple[str]] = {
"note.bass": ("planks",), "note.bass": ("planks",),
"note.snare": ("sand",), "note.snare": ("sand",),
"note.hat": ("glass",), "note.hat": ("glass",),
"note.bd": ("stone",), "note.bd": ("stone",),
"note.basedrum": ("stone",),
"note.bell": ("gold_block",), "note.bell": ("gold_block",),
"note.flute": ("clay",), "note.flute": ("clay",),
"note.chime": ("packed_ice",), "note.chime": ("packed_ice",),
@ -238,7 +255,11 @@ INSTRUMENT_BLOCKS_LIST = {
"note.banjo": ("hay_block",), "note.banjo": ("hay_block",),
"note.pling": ("glowstone",), "note.pling": ("glowstone",),
"note.bassattack": ("command_block",), # 无法找到此音效 "note.bassattack": ("command_block",), # 无法找到此音效
"note.harp": ("glass",), "note.harp": ("dirt",),
# 呃……
"firework.blast": ("sandstone",),
"firework.twinkle": ("red_sandstone",),
"fire.ignite": ("concrete_powder",),
} }

View File

@ -16,14 +16,144 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com # Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import random
from typing import Dict, List, Tuple, Union from typing import Dict, List, Tuple, Union
from .constants import INSTRUMENT_BLOCKS_TABLE
from .exceptions import * from .exceptions import *
from .main import MidiConvert from .main import MidiConvert
from .subclass import * from .subclass import *
from .utils import * from .utils import *
class FutureMidiConvertRSNB(MidiConvert):
"""
加入红石音乐适配
"""
music_command_list: Dict[int, SingleNoteBox]
"""音乐指令列表"""
@staticmethod
def soundID_to_block(sound_id: str, random_select: bool = False) -> str:
"""
将我的世界乐器名改作音符盒所需的对应方块名称
Parameters
----------
sound_id: str
将我的世界乐器名
random_select: bool
是否随机选取对应方块
Returns
-------
str方块名称
"""
try:
if random_select:
return random.choice(INSTRUMENT_BLOCKS_TABLE[sound_id])
else:
return INSTRUMENT_BLOCKS_TABLE[sound_id][0]
except KeyError:
return "air"
def to_note_list_in_delay(
self,
) -> Tuple[Dict[int, SingleNoteBox], int]:
"""
使用金羿的转换思路将midi转换为我的世界音符盒音高乐器及延迟列表并输出每个音符之后的延迟
Returns
-------
tuple( Dict[int, SingleNoteBox], int音乐时长游戏刻 )
"""
self.to_music_channels()
tracks = {}
note_range = {}
InstID = -1
# cmd_amount = 0
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else 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":
# block_id = self.soundID_to_block((
# self.perc_inst_to_soundID_withX(msg[1])
# if SpecialBits
# else self.inst_to_souldID_withX(InstID)
# )[0])
# delaytime_now = round(msg[-1] / 50)
note_ = 2 ** ((msg[1] - 60) / 12)
try:
tracks[msg[-1]].append(
(
InstID,
note_,
)
)
except KeyError:
tracks[msg[-1]] = [(InstID, note_)]
try:
note_range[InstID]["max"] = max(
note_range[InstID]["max"], note_
)
note_range[InstID]["min"] = min(
note_range[InstID]["min"], note_
)
except KeyError:
note_range[InstID] = {"max": note_, "min": note_}
del InstID
all_ticks = list(tracks.keys())
all_ticks.sort()
results = []
print(note_range)
exit()
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 FutureMidiConvertM4(MidiConvert): class FutureMidiConvertM4(MidiConvert):
""" """
加入插值算法优化音感 加入插值算法优化音感
@ -47,9 +177,7 @@ class FutureMidiConvertM4(MidiConvert):
] ]
# print(totalCount) # print(totalCount)
result: List[ result: List[Tuple[int, int, int, int, float],] = []
Tuple[int, int, int, int, float],
] = []
for _i in range(totalCount): for _i in range(totalCount):
result.append( result.append(
@ -105,7 +233,6 @@ class FutureMidiConvertM4(MidiConvert):
# nowChannel = [] # nowChannel = []
for track_no, track in self.channels[i].items(): for track_no, track in self.channels[i].items():
noteMsgs = [] noteMsgs = []
MsgIndex = [] MsgIndex = []
@ -152,7 +279,6 @@ class FutureMidiConvertM4(MidiConvert):
for every_note in self._linear_note( for every_note in self._linear_note(
note, 50 if note.track_no == 0 else 500 note, 50 if note.track_no == 0 else 500
): ):
soundID, _X = ( soundID, _X = (
self.perc_inst_to_soundID_withX(note.pitch) self.perc_inst_to_soundID_withX(note.pitch)
if SpecialBits if SpecialBits
@ -163,7 +289,13 @@ class FutureMidiConvertM4(MidiConvert):
max_score = max(max_score, score_now) max_score = max(max_score, score_now)
mc_pitch = 2 ** ((note.pitch - 60 - _X) / 12) 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 blockmeter = (
1
/ (1 if note.track_no == 0 else 0.9)
/ max_volume
/ every_note[4]
- 1
)
track_now.append( track_now.append(
SingleCommand( SingleCommand(
@ -237,7 +369,6 @@ class FutureMidiConvertM4(MidiConvert):
# nowChannel = [] # nowChannel = []
for track_no, track in self.channels[i].items(): for track_no, track in self.channels[i].items():
noteMsgs = [] noteMsgs = []
MsgIndex = [] MsgIndex = []
@ -282,7 +413,6 @@ class FutureMidiConvertM4(MidiConvert):
for every_note in self._linear_note( for every_note in self._linear_note(
note, 50 if note.track_no == 0 else 500 note, 50 if note.track_no == 0 else 500
): ):
soundID, _X = ( soundID, _X = (
self.perc_inst_to_soundID_withX(note.pitch) self.perc_inst_to_soundID_withX(note.pitch)
if SpecialBits if SpecialBits

View File

@ -31,69 +31,6 @@ Terms & Conditions: ../License.md
def _toCmdList_m1(
self,
scoreboardname: str = "mscplay",
volume: float = 1.0,
speed: float = 1.0) -> list:
"""
使用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.__Inst2soundID_withX(instrumentID)
singleTrack.append(
"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)}")
commands += 1
if len(singleTrack) != 0:
tracks.append(singleTrack)
return [tracks, commands, maxscore]
# ============================ # ============================
@ -209,16 +146,13 @@ if __name__ == '__main__':
# ============================ # ============================
from typing import Literal
from ..constants import x,y,z
from typing import Union
from .plugin import x,y,z,bottem_side_length_of_smallest_square_bottom_box,form_note_block_in_NBT_struct,form_repeater_in_NBT_struct
# 不要用 没写完 # 不要用 没写完
def delay_to_note_blocks( def delay_to_note_blocks(
baseblock: str = "stone", baseblock: str = "stone",
position_forward: Union(x, y, z) = z, position_forward: Literal['x','y','z'] = z,
max_height: int = 64,
): ):
"""传入音符,生成以音符盒存储的红石音乐 """传入音符,生成以音符盒存储的红石音乐
:param: :param:
@ -229,10 +163,6 @@ def delay_to_note_blocks(
from TrimMCStruct import Structure, Block from TrimMCStruct import Structure, Block
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
len(commands), max_height
)
struct = Structure( struct = Structure(
(_sideLength, max_height, _sideLength), # 声明结构大小 (_sideLength, max_height, _sideLength), # 声明结构大小
) )
@ -292,5 +222,4 @@ def delay_to_note_blocks(
struct.set_block(Block("universal_minecraft", baseblock), startpos) struct.set_block(Block("universal_minecraft", baseblock), startpos)
startpos[0] += posadder[0] startpos[0] += posadder[0]
startpos[1] += posadder[1] startpos[1] += posadder[1]
startpos[2] += posadder[2] startpos[2] += posadder[2]

View File

@ -25,7 +25,6 @@ Terms & Conditions: License.md in the root directory
# 赶快呼叫 程序员Let's Go 直ぐに呼びましょプログラマ レッツゴー! # 赶快呼叫 程序员Let's Go 直ぐに呼びましょプログラマ レッツゴー!
import math import math
import os import os
from typing import List, Literal, Tuple, Union from typing import List, Literal, Tuple, Union
@ -183,12 +182,19 @@ class MidiConvert:
"""文件名,不含路径且不含后缀""" """文件名,不含路径且不含后缀"""
try: try:
return cls(mido.MidiFile(midi_file_path,clip=True), midi_music_name, old_exe_format) return cls(
mido.MidiFile(midi_file_path, clip=True),
midi_music_name,
old_exe_format,
)
except (ValueError, TypeError) as E: except (ValueError, TypeError) as E:
raise MidiDestroyedError(f"文件{midi_file_path}损坏:{E}") raise MidiDestroyedError(f"文件{midi_file_path}损坏:{E}")
except FileNotFoundError as E: except FileNotFoundError as E:
raise FileNotFoundError(f"文件{midi_file_path}不存在:{E}") raise FileNotFoundError(f"文件{midi_file_path}不存在:{E}")
# ……真的那么重要吗
# 我又几曾何时,知道祂真的会抛下我
@staticmethod @staticmethod
def inst_to_souldID_withX( def inst_to_souldID_withX(
instrumentID: int, instrumentID: int,
@ -214,7 +220,7 @@ class MidiConvert:
tuple(str我的世界乐器名, int转换算法中的X) tuple(str我的世界乐器名, int转换算法中的X)
""" """
try: try:
return PITCHED_INSTRUMENT_LIST[instrumentID] return PITCHED_INSTRUMENT_TABLE[instrumentID]
except KeyError: except KeyError:
return "note.flute", 5 return "note.flute", 5
@ -233,17 +239,12 @@ class MidiConvert:
tuple(str我的世界乐器名, int转换算法中的X) tuple(str我的世界乐器名, int转换算法中的X)
""" """
try: try:
return PERCUSSION_INSTRUMENT_LIST[instrumentID] return PERCUSSION_INSTRUMENT_TABLE[instrumentID]
except KeyError: except KeyError:
print("WARN", f"无法使用打击乐器列表库或者使用了不存在的乐器打击乐器使用Dislink算法代替。{instrumentID}") return "note.bd", 7
if instrumentID == 55:
return "note.cow_bell", 5 # 明明已经走了
elif instrumentID in [41, 43, 45]: # 凭什么还要在我心里留下缠绵缱绻
return "note.hat", 7
elif instrumentID in [36, 37, 39]:
return "note.snare", 7
else:
return "note.bd", 7
def form_progress_bar( def form_progress_bar(
self, self,
@ -356,6 +357,10 @@ class MidiConvert:
) )
) )
# 那是假的
# 一切都并未留下痕迹啊
# 那梦又是多么的真实……
if r"%%t" in pgs_style: if r"%%t" in pgs_style:
result.append( result.append(
SingleCommand( SingleCommand(
@ -552,13 +557,12 @@ class MidiConvert:
if msg.type == "set_tempo": if msg.type == "set_tempo":
tempo = msg.tempo tempo = msg.tempo
else: else:
try: try:
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] = []
except AttributeError as E: except AttributeError as E:
print(msg,E) print(msg, E)
if msg.type == "program_change": if msg.type == "program_change":
midi_channels[msg.channel][track_no].append( midi_channels[msg.channel][track_no].append(
("PgmC", msg.program, microseconds) ("PgmC", msg.program, microseconds)
@ -576,7 +580,6 @@ class MidiConvert:
("NoteE", msg.note, microseconds) ("NoteE", msg.note, microseconds)
) )
"""整合后的音乐通道格式 """整合后的音乐通道格式
每个通道包括若干消息元素其中逃不过这三种 每个通道包括若干消息元素其中逃不过这三种
@ -689,7 +692,7 @@ class MidiConvert:
max_volume: float = 1.0, max_volume: float = 1.0,
speed: float = 1.0, speed: float = 1.0,
player_selector: str = "@a", player_selector: str = "@a",
) -> Tuple[List[SingleCommand], int]: ) -> Tuple[List[SingleCommand], int, int]:
""" """
使用金羿的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟 使用金羿的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟
@ -704,7 +707,7 @@ class MidiConvert:
Returns Returns
------- -------
tuple( list[SingleCommand,...], int音乐时长游戏刻 ) tuple( list[SingleCommand,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
""" """
if speed == 0: if speed == 0:
@ -740,10 +743,10 @@ class MidiConvert:
else self.inst_to_souldID_withX(InstID) else self.inst_to_souldID_withX(InstID)
) )
score_now = round(msg[-1] / float(speed) / 50) delaytime_now = round(msg[-1] / float(speed) / 50)
try: try:
tracks[score_now].append( tracks[delaytime_now].append(
self.execute_cmd_head.format(player_selector) self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} " + f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
+ ( + (
@ -753,7 +756,7 @@ class MidiConvert:
) )
) )
except KeyError: except KeyError:
tracks[score_now] = [ tracks[delaytime_now] = [
self.execute_cmd_head.format(player_selector) self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} " + f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
+ ( + (
@ -771,8 +774,10 @@ class MidiConvert:
all_ticks = list(tracks.keys()) all_ticks = list(tracks.keys())
all_ticks.sort() all_ticks.sort()
results = [] results = []
max_multi = 0
for i in range(len(all_ticks)): for i in range(len(all_ticks)):
max_multi = max(max_multi, len(tracks[all_ticks[i]]))
for j in range(len(tracks[all_ticks[i]])): for j in range(len(tracks[all_ticks[i]])):
results.append( results.append(
SingleCommand( SingleCommand(
@ -794,7 +799,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, max_multi
def copy_important(self): def copy_important(self):
dst = MidiConvert( dst = MidiConvert(

View File

@ -148,7 +148,6 @@ def commands_to_BDX_bytes(
now_x = 0 now_x = 0
for command in commands_list: for command in commands_list:
_bytes += form_command_block_in_BDX_bytes( _bytes += form_command_block_in_BDX_bytes(
command.command_text, command.command_text,
(1 if y_forward else 0) (1 if y_forward else 0)
@ -156,12 +155,14 @@ def commands_to_BDX_bytes(
((now_y != 0) and (not y_forward)) ((now_y != 0) and (not y_forward))
or (y_forward and (now_y != (max_height - 1))) or (y_forward and (now_y != (max_height - 1)))
) )
else (3 if z_forward else 2) else (
if ( (3 if z_forward else 2)
((now_z != 0) and (not z_forward)) if (
or (z_forward and (now_z != _sideLength - 1)) ((now_z != 0) and (not z_forward))
) or (z_forward and (now_z != _sideLength - 1))
else 5, )
else 5
),
impluse=2, impluse=2,
condition=command.conditional, condition=command.conditional,
needRedstone=False, needRedstone=False,
@ -171,6 +172,16 @@ def commands_to_BDX_bytes(
trackOutput=True, trackOutput=True,
) )
# (1 if y_forward else 0) if ( # 如果y+则向上,反之向下
# ((now_y != 0) and (not y_forward)) # 如果不是y轴上首个方块
# or (y_forward and (now_y != (max_height - 1))) # 如果不是y轴上末端方块
# ) else ( # 否则即是y轴末端或首个方块
# (3 if z_forward else 2) if ( # 如果z+则向z轴正方向反之负方向
# ((now_z != 0) and (not z_forward)) # 如果不是z轴上的首个方块
# or (z_forward and (now_z != _sideLength - 1)) # 如果不是z轴上的末端方块
# ) else 5 # 否则则要面向x轴正方向
# )
now_y += 1 if y_forward else -1 now_y += 1 if y_forward else -1
if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)): if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)):

View File

@ -154,7 +154,7 @@ def to_BDX_file_in_delay(
data_cfg.volume_ratio, data_cfg.volume_ratio,
data_cfg.speed_multiplier, data_cfg.speed_multiplier,
player, player,
) )[:2]
if not os.path.exists(data_cfg.dist_path): if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path) os.makedirs(data_cfg.dist_path)

View File

@ -25,3 +25,5 @@ def bottem_side_length_of_smallest_square_bottom_box(total: int, maxHeight: int)
:param maxHeight: 最大高度 :param maxHeight: 最大高度
:return: 外切正方形的边长 int""" :return: 外切正方形的边长 int"""
return math.ceil(math.sqrt(math.ceil(total / maxHeight))) return math.ceil(math.sqrt(math.ceil(total / maxHeight)))

View File

@ -15,7 +15,8 @@ Terms & Conditions: License.md in the root directory
__all__ = [ __all__ = [
"to_mcstructure_file_in_delay" "to_mcstructure_file_in_delay",
"to_mcstructure_file_in_redstone_CD",
] ]
__author__ = (("金羿", "Eilles Wan"),) __author__ = (("金羿", "Eilles Wan"),)

View File

@ -13,10 +13,12 @@ Terms & Conditions: License.md in the root directory
import os import os
from typing import Literal
from ...exceptions import CommandFormatError from ...exceptions import CommandFormatError
from ...main import MidiConvert from ...main import MidiConvert
from ..main import ConvertConfig from ..main import ConvertConfig
from ..mcstructure import commands_to_structure from ..mcstructure import commands_to_structure,commands_to_redstone_delay_structure
def to_mcstructure_file_in_delay( def to_mcstructure_file_in_delay(
@ -51,7 +53,7 @@ def to_mcstructure_file_in_delay(
data_cfg.volume_ratio, data_cfg.volume_ratio,
data_cfg.speed_multiplier, data_cfg.speed_multiplier,
player, player,
) )[:2]
if not os.path.exists(data_cfg.dist_path): if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path) os.makedirs(data_cfg.dist_path)
@ -69,8 +71,56 @@ def to_mcstructure_file_in_delay(
return size, max_delay return size, max_delay
def to_mcstructure_file_in_redstone(
def to_mcstructure_file_in_redstone_CD(
midi_cvt: MidiConvert, midi_cvt: MidiConvert,
data_cfg: ConvertConfig, data_cfg: ConvertConfig,
player: str = "@a",
axis_side: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"] = "z+",
basement_block: str = "concrete",
): ):
pass """
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
player: str
玩家选择器默认为`@a`
axis_side: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"]
生成结构的延展方向
basement_block: str
结构的基底方块
Returns
-------
tuple[tuple[int,]结构大小, int音乐总延迟]
"""
if midi_cvt.enable_old_exe_format:
raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。")
cmd_list, max_delay, max_multiple_cmd = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
struct, size, end_pos = commands_to_redstone_delay_structure(cmd_list,max_delay,max_multiple_cmd, basement_block, axis_side)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.mcstructure")
),
"wb+",
) as f:
struct.dump(f)
return size, max_delay

View File

@ -56,7 +56,7 @@ def to_mcstructure_addon_in_delay(
data_cfg.volume_ratio, data_cfg.volume_ratio,
data_cfg.speed_multiplier, data_cfg.speed_multiplier,
player, player,
) )[:2]
if not os.path.exists(data_cfg.dist_path): if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path) os.makedirs(data_cfg.dist_path)

View File

@ -16,12 +16,40 @@ Terms & Conditions: License.md in the root directory
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import List, Tuple from typing import List, Literal, Tuple
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
from ..constants import x, y, z
from ..subclass import SingleCommand from ..subclass import SingleCommand
from .common import bottem_side_length_of_smallest_square_bottom_box from .common import bottem_side_length_of_smallest_square_bottom_box
import struct
def antiaxis(axis: Literal['x','z','X','Z']):
return z if axis == x else x
def forward_IER(forward: bool):
return 1 if forward else -1
AXIS_PARTICULAR_VALUE = {
x: {
True: 5,
False: 4,
},
y: {
True: 1,
False: 0,
},
z: {
True: 3,
False: 2,
},
}
def command_statevalue(axis_: Literal['x','y','z','X','Y','Z'],forward_:bool):
return AXIS_PARTICULAR_VALUE[axis_.lower()][forward_]
def form_note_block_in_NBT_struct( def form_note_block_in_NBT_struct(
@ -64,8 +92,12 @@ def form_note_block_in_NBT_struct(
def form_repeater_in_NBT_struct(delay: int, facing: int): def form_repeater_in_NBT_struct(delay: int, facing: int):
"""生成中继器方块 """生成中继器方块
:param facing: :param facing: 朝向
:param delay: 1~4 Z- 0
X- 1
Z+ 2
X+ 西 3
:param delay: 0~3
:return Block()""" :return Block()"""
return Block( return Block(
@ -165,7 +197,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 } # type: ignore
}, },
compability_version=17959425, compability_version=17959425,
) )
@ -251,3 +283,109 @@ def commands_to_structure(
), ),
(now_x, now_y, now_z), (now_x, now_y, now_z),
) )
def commands_to_redstone_delay_structure(
commands: List[SingleCommand],
delay_length: int,
max_multicmd_length: int,
base_block: str = "concrete",
axis_: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"] = "z+",
) -> Tuple[Structure, Tuple[int, int, int], Tuple[int, int, int]]:
"""
:param commands: 指令列表
:param delay_length: 延时总长
:param max_multicmd_length: 最大同时播放的音符数量
:param base_block: 生成结构的基底方块
:param axis_: 生成结构的延展方向
:return 结构类,结构占用大小,终点坐标
"""
if axis_ in ["z+", "Z+"]:
extensioon_direction = z
aside_direction = x
repeater_facing = 2
forward = True
elif axis_ in ["z-", "Z-"]:
extensioon_direction = z
aside_direction = x
repeater_facing = 0
forward = False
elif axis_ in ["x+", "X+"]:
extensioon_direction = x
aside_direction = z
repeater_facing = 3
forward = True
elif axis_ in ["x-", "X-"]:
extensioon_direction = x
aside_direction = z
repeater_facing = 1
forward = False
else:
raise ValueError(f"axis_({axis_}) 参数错误。")
goahead = forward_IER(forward)
struct = Structure(
(round(delay_length/2+0.5+len(commands)) if extensioon_direction == x else max_multicmd_length, 3, round(delay_length/2+0.5+len(commands)) if extensioon_direction == z else max_multicmd_length)
)
pos_now = {x:(0 if forward else struct.size[0]),y:0,z:(0 if forward else struct.size[2])}
first_impluse = True
for cmd in commands:
single_repeater_value = round(cmd.delay / 2) % 4 - 1
additional_repeater = round(cmd.delay / 2)// 4
for i in range(additional_repeater):
struct.set_block(
tuple(pos_now.values()),
Block("minecraft", base_block,),
)
struct.set_block(
(pos_now[x],1,pos_now[z]),
form_repeater_in_NBT_struct(
delay=3,
facing=repeater_facing,
),
)
pos_now[extensioon_direction] += goahead
first_impluse = True
if single_repeater_value >= 0:
struct.set_block(
tuple(pos_now.values()),
Block("minecraft", base_block,),
)
struct.set_block(
(pos_now[x],1,pos_now[z]),
form_repeater_in_NBT_struct(
delay=single_repeater_value,
facing=repeater_facing,
),
)
pos_now[extensioon_direction] += goahead
first_impluse = True
struct.set_block(
(pos_now[x],1,pos_now[z]),
form_command_block_in_NBT_struct(
command=cmd.command_text,
coordinate=(pos_now[x],1,pos_now[z]),
particularValue=command_statevalue(extensioon_direction, forward),
# impluse= (0 if first_impluse else 2),
impluse=0,
condition=False,
alwaysRun=False,
tickDelay=0,
customName=cmd.annotation_text,
),
)
struct.set_block(
(pos_now[x],2,pos_now[z]),
Block("minecraft", "redstone_wire"),
)
pos_now[extensioon_direction] += goahead
first_impluse = False
return struct, struct.size, tuple(pos_now.values())

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
"""
存放有关Schematic结构生成的内容
"""
"""
版权所有 © 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
import nbtschematic

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
"""
用以生成Schematic结构的附加功能
版权所有 © 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
__all__ = [
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@ -0,0 +1,14 @@
# -*- 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 ..schematic import *

View File

@ -98,6 +98,63 @@ class ObsoleteMidiConvert(MidiConvert):
return [tracks, commands, maxscore] return [tracks, commands, maxscore]
def _toCmdList_m1(
self,
scoreboardname: str = "mscplay",
volume: float = 1.0,
speed: float = 1.0) -> list:
"""
使用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.__Inst2soundID_withX(instrumentID)
singleTrack.append(
"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)}")
commands += 1
if len(singleTrack) != 0:
tracks.append(singleTrack)
return [tracks, commands, maxscore]
# 原本这个算法的转换效果应该和上面的算法相似的 # 原本这个算法的转换效果应该和上面的算法相似的
def _toCmdList_m2( def _toCmdList_m2(
self: MidiConvert, self: MidiConvert,

View File

@ -18,6 +18,9 @@ Terms & Conditions: License.md in the root directory
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional
from .constants import PERCUSSION_INSTRUMENT_LIST
@dataclass(init=False) @dataclass(init=False)
@ -43,7 +46,13 @@ class SingleNote:
"""音符所处的音轨""" """音符所处的音轨"""
def __init__( def __init__(
self, instrument: int, pitch: int, velocity: int, startTime: int, lastTime: int, track_number:int = 0 self,
instrument: int,
pitch: int,
velocity: int,
startTime: int,
lastTime: int,
track_number: int = 0,
): ):
"""用于存储单个音符的类 """用于存储单个音符的类
:param instrument 乐器编号 :param instrument 乐器编号
@ -97,6 +106,11 @@ class SingleNote:
"lastTime": self.duration, "lastTime": self.duration,
} }
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()
@dataclass(init=False) @dataclass(init=False)
class SingleCommand: class SingleCommand:
@ -170,3 +184,96 @@ 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__()
@dataclass(init=False)
class SingleNoteBox:
"""存储单个音符盒"""
instrument_block: str
"""乐器方块"""
note_value: int
"""音符盒音高"""
annotation_text: str
"""音符注释"""
is_percussion: bool
"""是否为打击乐器"""
def __init__(
self,
instrument_block_: str,
note_value_: int,
percussion: Optional[bool] = None,
annotation: str = "",
):
"""用于存储单个音符盒的类
:param instrument_block_ 音符盒演奏所使用的乐器方块
:param note_value_ 音符盒的演奏音高
:param percussion 此音符盒乐器是否作为打击乐处理
若为空则自动识别是否为打击乐器
:param annotation 音符注释"""
self.instrument_block = instrument_block_
"""乐器方块"""
self.note_value = note_value_
"""音符盒音高"""
self.annotation_text = annotation
"""音符注释"""
if percussion is None:
self.is_percussion = percussion in PERCUSSION_INSTRUMENT_LIST
else:
self.is_percussion = percussion
@property
def inst(self) -> str:
"""获取音符盒下的乐器方块"""
return self.instrument_block
@inst.setter
def inst(self, inst_):
self.instrument_block = inst_
@property
def note(self) -> int:
"""获取音符盒音调特殊值"""
return self.note_value
@note.setter
def note(self, note_):
self.note_value = note_
@property
def annotation(self) -> str:
"""获取音符盒的备注"""
return self.annotation_text
@annotation.setter
def annotation(self, annotation_):
self.annotation_text = annotation_
def copy(self):
return SingleNoteBox(
instrument_block_=self.instrument_block,
note_value_=self.note_value,
annotation=self.annotation_text,
)
def __str__(self) -> str:
return f"Note(inst = {self.inst}, note = {self.note}, )"
def __tuple__(self) -> tuple:
return self.inst, self.note, self.annotation
def __dict__(self) -> dict:
return {
"inst": self.inst,
"note": self.note,
"annotation": self.annotation,
}
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()

8
RSMC_test.py Normal file
View File

@ -0,0 +1,8 @@
import Musicreater.experiment
print(
Musicreater.experiment.FutureMidiConvertRSNB.from_midi_file(
input("midi路径:"), old_exe_format=False
).to_note_list_in_delay()
)

View File

@ -0,0 +1,13 @@
import Musicreater
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
print(
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_redstone_CD(
Musicreater.MidiConvert.from_midi_file(input("midi路径:"), old_exe_format=False),
Musicreater.plugin.ConvertConfig(
input("输出路径:"),
volume=1
),
)
)

View File

@ -1,2 +1 @@
LyricLib>=0.0.3
mido>=1.3 mido>=1.3