mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2025-02-07 19:36:46 +08:00
改进Channel的应用,减少内存占用,优化结构生成,提高MIDI兼容,修改API结构
This commit is contained in:
parent
9a580132e5
commit
72dfdfeb34
@ -17,8 +17,8 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__version__ = "1.5.2"
|
__version__ = "1.6.0"
|
||||||
__vername__ = "添加midi对应的键盘符号表"
|
__vername__ = "切换ChannelType类型为NoteChannelType类型"
|
||||||
__author__ = (
|
__author__ = (
|
||||||
("金羿", "Eilles Wan"),
|
("金羿", "Eilles Wan"),
|
||||||
("诸葛亮与八卦阵", "bgArray"),
|
("诸葛亮与八卦阵", "bgArray"),
|
||||||
@ -29,7 +29,7 @@ __all__ = [
|
|||||||
# 主要类
|
# 主要类
|
||||||
"MidiConvert",
|
"MidiConvert",
|
||||||
# 附加类
|
# 附加类
|
||||||
# "SingleNote",
|
"SingleNote",
|
||||||
"SingleCommand",
|
"SingleCommand",
|
||||||
# "TimeStamp", 未来功能
|
# "TimeStamp", 未来功能
|
||||||
]
|
]
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from typing import Dict, List, Tuple
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存放常量与数值性内容
|
存放常量与数值性内容
|
||||||
"""
|
"""
|
||||||
@ -18,6 +16,7 @@ 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
|
||||||
|
|
||||||
x = "x"
|
x = "x"
|
||||||
"""
|
"""
|
||||||
@ -368,7 +367,7 @@ PERCUSSION_INSTRUMENT_LIST: List[str] = [
|
|||||||
"fire.ignite",
|
"fire.ignite",
|
||||||
]
|
]
|
||||||
|
|
||||||
INSTRUMENT_BLOCKS_TABLE: Dict[str, Tuple[str]] = {
|
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",),
|
||||||
|
@ -24,7 +24,7 @@ class MSCTBaseException(Exception):
|
|||||||
"""音·创库版本的所有错误均继承于此"""
|
"""音·创库版本的所有错误均继承于此"""
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
|
||||||
def miao(
|
def meow(
|
||||||
self,
|
self,
|
||||||
):
|
):
|
||||||
for i in self.args:
|
for i in self.args:
|
||||||
@ -66,12 +66,15 @@ class CommandFormatError(RuntimeError):
|
|||||||
super().__init__("指令格式不匹配", *args)
|
super().__init__("指令格式不匹配", *args)
|
||||||
|
|
||||||
|
|
||||||
class CrossNoteError(MidiFormatException):
|
# class CrossNoteError(MidiFormatException):
|
||||||
"""同通道下同音符交叉出现所产生的错误"""
|
# """同通道下同音符交叉出现所产生的错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
# def __init__(self, *args):
|
||||||
"""同通道下同音符交叉出现所产生的错误"""
|
# """同通道下同音符交叉出现所产生的错误"""
|
||||||
super().__init__("同通道下同音符交叉", *args)
|
# super().__init__("同通道下同音符交叉", *args)
|
||||||
|
# 这TM是什么错误?
|
||||||
|
# 我什么时候写的这玩意?
|
||||||
|
# 我哪知道这说的是啥?
|
||||||
|
|
||||||
|
|
||||||
class NotDefineTempoError(MidiFormatException):
|
class NotDefineTempoError(MidiFormatException):
|
||||||
@ -98,7 +101,15 @@ class NotDefineProgramError(MidiFormatException):
|
|||||||
super().__init__("未指定演奏乐器", *args)
|
super().__init__("未指定演奏乐器", *args)
|
||||||
|
|
||||||
|
|
||||||
class ZeroSpeedError(MidiFormatException):
|
class NoteOnOffMismatchError(MidiFormatException):
|
||||||
|
"""音符开音和停止不匹配的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音符开音和停止不匹配的错误"""
|
||||||
|
super().__init__("音符不匹配", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class ZeroSpeedError(ZeroDivisionError):
|
||||||
"""以0作为播放速度的错误"""
|
"""以0作为播放速度的错误"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
|
@ -17,14 +17,13 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
import random
|
import random
|
||||||
from typing import Dict, List, Tuple, Union
|
|
||||||
|
|
||||||
from .constants import INSTRUMENT_BLOCKS_TABLE
|
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 *
|
||||||
|
from .types import Tuple, List, Dict
|
||||||
|
|
||||||
class FutureMidiConvertRSNB(MidiConvert):
|
class FutureMidiConvertRSNB(MidiConvert):
|
||||||
"""
|
"""
|
||||||
|
@ -27,39 +27,13 @@ Terms & Conditions: License.md in the root directory
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
from typing import List, Literal, Tuple, Union
|
|
||||||
|
|
||||||
import mido
|
|
||||||
|
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .subclass import *
|
from .subclass import *
|
||||||
|
from .types import *
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
||||||
VoidMido = Union[mido.MidiFile, None] # void mido
|
|
||||||
"""
|
|
||||||
空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 毫秒每四分音符,换句话说就是一拍占多少毫秒
|
||||||
@ -112,7 +86,7 @@ class MidiConvert:
|
|||||||
execute_cmd_head: str
|
execute_cmd_head: str
|
||||||
"""execute指令头部"""
|
"""execute指令头部"""
|
||||||
|
|
||||||
channels: ChannelType
|
channels: Union[ChannelType, NoteChannelType]
|
||||||
"""频道信息字典"""
|
"""频道信息字典"""
|
||||||
|
|
||||||
music_command_list: List[SingleCommand]
|
music_command_list: List[SingleCommand]
|
||||||
@ -195,57 +169,6 @@ class MidiConvert:
|
|||||||
# ……真的那么重要吗
|
# ……真的那么重要吗
|
||||||
# 我又几曾何时,知道祂真的会抛下我
|
# 我又几曾何时,知道祂真的会抛下我
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def inst_to_souldID_withX(
|
|
||||||
instrumentID: int,
|
|
||||||
) -> Tuple[str, int]:
|
|
||||||
"""
|
|
||||||
返回midi的乐器ID对应的我的世界乐器名,对于音域转换算法,如下:
|
|
||||||
2**( ( msg.note - 60 - X ) / 12 ) 即为MC的音高,其中
|
|
||||||
X的取值随乐器不同而变化:
|
|
||||||
竖琴harp、电钢琴pling、班卓琴banjo、方波bit、颤音琴iron_xylophone 的时候为6
|
|
||||||
吉他的时候为7
|
|
||||||
贝斯bass、迪吉里杜管didgeridoo的时候为8
|
|
||||||
长笛flute、牛铃cou_bell的时候为5
|
|
||||||
钟琴bell、管钟chime、木琴xylophone的时候为4
|
|
||||||
而存在一些打击乐器bd(basedrum)、hat、snare,没有音域,则没有X,那么我们返回7即可
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
instrumentID: int
|
|
||||||
midi的乐器ID
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
tuple(str我的世界乐器名, int转换算法中的X)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return PITCHED_INSTRUMENT_TABLE[instrumentID]
|
|
||||||
except KeyError:
|
|
||||||
return "note.flute", 5
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def perc_inst_to_soundID_withX(instrumentID: int) -> Tuple[str, int]:
|
|
||||||
"""
|
|
||||||
对于Midi第10通道所对应的打击乐器,返回我的世界乐器名
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
instrumentID: int
|
|
||||||
midi的乐器ID
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
tuple(str我的世界乐器名, int转换算法中的X)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return PERCUSSION_INSTRUMENT_TABLE[instrumentID]
|
|
||||||
except KeyError:
|
|
||||||
return "note.bd", 7
|
|
||||||
|
|
||||||
# 明明已经走了
|
|
||||||
# 凭什么还要在我心里留下缠绵缱绻
|
|
||||||
|
|
||||||
def form_progress_bar(
|
def form_progress_bar(
|
||||||
self,
|
self,
|
||||||
max_score: int,
|
max_score: int,
|
||||||
@ -522,24 +445,26 @@ class MidiConvert:
|
|||||||
self.progress_bar_command = result
|
self.progress_bar_command = result
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def to_music_channels(
|
def to_music_note_channels(
|
||||||
self,
|
self,
|
||||||
) -> ChannelType:
|
ignore_mismatch_error: bool = True,
|
||||||
|
) -> NoteChannelType:
|
||||||
"""
|
"""
|
||||||
使用金羿的转换思路,将midi解析并转换为频道信息字典
|
将midi解析并转换为频道音符字典
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
以频道作为分割的Midi信息字典:
|
以频道作为分割的Midi音符列表字典:
|
||||||
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
|
Dict[int,List[SingleNote,]]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.midi is None:
|
if self.midi is None:
|
||||||
raise MidiUnboundError(
|
raise MidiUnboundError(
|
||||||
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。"
|
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象?这是不可复用的。"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
midi_channels: ChannelType = empty_midi_channels()
|
midi_channels: NoteChannelType = empty_midi_channels(staff=[])
|
||||||
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
|
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||||
|
|
||||||
# 我们来用通道统计音乐信息
|
# 我们来用通道统计音乐信息
|
||||||
@ -549,6 +474,27 @@ class MidiConvert:
|
|||||||
if not track:
|
if not track:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
note_queue_A: Dict[
|
||||||
|
int,
|
||||||
|
List[
|
||||||
|
Tuple[
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
] = empty_midi_channels(staff=[])
|
||||||
|
note_queue_B: Dict[
|
||||||
|
int,
|
||||||
|
List[
|
||||||
|
Tuple[
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
] = empty_midi_channels(staff=[])
|
||||||
|
|
||||||
|
channel_program: Dict[int, int] = empty_midi_channels(staff=-1)
|
||||||
|
|
||||||
for msg in track:
|
for msg in track:
|
||||||
if msg.time != 0:
|
if msg.time != 0:
|
||||||
microseconds += msg.time * tempo / self.midi.ticks_per_beat / 1000
|
microseconds += msg.time * tempo / self.midi.ticks_per_beat / 1000
|
||||||
@ -557,27 +503,57 @@ class MidiConvert:
|
|||||||
if msg.type == "set_tempo":
|
if msg.type == "set_tempo":
|
||||||
tempo = msg.tempo
|
tempo = msg.tempo
|
||||||
else:
|
else:
|
||||||
try:
|
|
||||||
if not track_no in midi_channels[msg.channel].keys():
|
|
||||||
midi_channels[msg.channel][track_no] = []
|
|
||||||
except AttributeError as E:
|
|
||||||
print(msg, E)
|
|
||||||
|
|
||||||
if msg.type == "program_change":
|
if msg.type == "program_change":
|
||||||
midi_channels[msg.channel][track_no].append(
|
channel_program[msg.channel] = msg.program
|
||||||
("PgmC", msg.program, microseconds)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif msg.type == "note_on" and msg.velocity != 0:
|
elif msg.type == "note_on" and msg.velocity != 0:
|
||||||
midi_channels[msg.channel][track_no].append(
|
note_queue_A[msg.channel].append(
|
||||||
("NoteS", msg.note, msg.velocity, microseconds)
|
(msg.note, channel_program[msg.channel])
|
||||||
)
|
)
|
||||||
|
note_queue_B[msg.channel].append((msg.velocity, microseconds))
|
||||||
|
|
||||||
elif (msg.type == "note_on" and msg.velocity == 0) or (
|
elif (msg.type == "note_off") or (
|
||||||
msg.type == "note_off"
|
msg.type == "note_on" and msg.velocity == 0
|
||||||
):
|
):
|
||||||
midi_channels[msg.channel][track_no].append(
|
if (msg.note, channel_program[msg.channel]) in note_queue_A[
|
||||||
("NoteE", msg.note, microseconds)
|
msg.channel
|
||||||
|
]:
|
||||||
|
_velocity, _ms = note_queue_B[msg.channel][
|
||||||
|
note_queue_A[msg.channel].index(
|
||||||
|
(msg.note, channel_program[msg.channel])
|
||||||
|
)
|
||||||
|
]
|
||||||
|
note_queue_A[msg.channel].remove(
|
||||||
|
(msg.note, channel_program[msg.channel])
|
||||||
|
)
|
||||||
|
note_queue_B[msg.channel].remove((_velocity, _ms))
|
||||||
|
midi_channels[msg.channel].append(
|
||||||
|
SingleNote(
|
||||||
|
instrument=msg.note,
|
||||||
|
pitch=channel_program[msg.channel],
|
||||||
|
velocity=_velocity,
|
||||||
|
startime=_ms,
|
||||||
|
lastime=microseconds - _ms,
|
||||||
|
track_number=track_no,
|
||||||
|
is_percussion=True,
|
||||||
|
)
|
||||||
|
if msg.channel == 9
|
||||||
|
else SingleNote(
|
||||||
|
instrument=channel_program[msg.channel],
|
||||||
|
pitch=msg.note,
|
||||||
|
velocity=_velocity,
|
||||||
|
startime=_ms,
|
||||||
|
lastime=microseconds - _ms,
|
||||||
|
track_number=track_no,
|
||||||
|
is_percussion=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if ignore_mismatch_error:
|
||||||
|
print("[WARRING] MIDI格式错误 音符不匹配 {} 无法在上文中找到与之匹配的音符开音消息".format(msg))
|
||||||
|
else:
|
||||||
|
raise NoteOnOffMismatchError(
|
||||||
|
"当前的MIDI很可能有损坏之嫌……", msg, "无法在上文中找到与之匹配的音符开音消息。"
|
||||||
)
|
)
|
||||||
|
|
||||||
"""整合后的音乐通道格式
|
"""整合后的音乐通道格式
|
||||||
@ -592,9 +568,14 @@ class MidiConvert:
|
|||||||
3 音符结束消息
|
3 音符结束消息
|
||||||
("NoteE", 结束的音符ID, 距离演奏开始的毫秒)"""
|
("NoteE", 结束的音符ID, 距离演奏开始的毫秒)"""
|
||||||
del tempo, self.channels
|
del tempo, self.channels
|
||||||
self.channels = midi_channels
|
self.channels = dict(
|
||||||
# [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
|
(channel_no, sorted(channel_notes, key=lambda note: note.start_time))
|
||||||
|
for channel_no, channel_notes in midi_channels.items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.channels
|
||||||
|
|
||||||
def to_command_list_in_score(
|
def to_command_list_in_score(
|
||||||
self,
|
self,
|
||||||
@ -623,75 +604,46 @@ class MidiConvert:
|
|||||||
raise ZeroSpeedError("播放速度仅可为正实数")
|
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||||
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
||||||
|
|
||||||
tracks = []
|
command_channels = []
|
||||||
cmdAmount = 0
|
command_amount = 0
|
||||||
maxScore = 0
|
max_score = 0
|
||||||
InstID = -1
|
|
||||||
|
|
||||||
self.to_music_channels()
|
|
||||||
|
|
||||||
# 此处 我们把通道视为音轨
|
# 此处 我们把通道视为音轨
|
||||||
for i in self.channels.keys():
|
for channel in self.to_music_note_channels().values():
|
||||||
# 如果当前通道为空 则跳过
|
# 如果当前通道为空 则跳过
|
||||||
if not self.channels[i]:
|
if not channel:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 第十通道是打击乐通道
|
this_channel = []
|
||||||
SpecialBits = True if i == 9 else False
|
|
||||||
|
|
||||||
for track_no, track in self.channels[i].items():
|
for note in channel:
|
||||||
nowTrack = []
|
score_now = round(note.start_time / float(speed) / 50)
|
||||||
|
max_score = max(max_score, score_now)
|
||||||
|
|
||||||
for msg in track:
|
this_channel.append(
|
||||||
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)
|
|
||||||
mc_pitch = "" if SpecialBits else 2 ** ((msg[1] - 60 - _X) / 12)
|
|
||||||
mc_distance_volume = 128 / max_volume / msg[2] + (
|
|
||||||
1 if SpecialBits else -1
|
|
||||||
)
|
|
||||||
|
|
||||||
nowTrack.append(
|
|
||||||
SingleCommand(
|
SingleCommand(
|
||||||
self.execute_cmd_head.format(
|
self.execute_cmd_head.format(
|
||||||
"@a[scores=({}={})]".format(
|
"@a[scores=({}={})]".format(scoreboard_name, score_now)
|
||||||
scoreboard_name, score_now
|
|
||||||
)
|
|
||||||
.replace("(", r"{")
|
.replace("(", r"{")
|
||||||
.replace(")", r"}")
|
.replace(")", r"}")
|
||||||
)
|
)
|
||||||
+ "playsound {} @s ^ ^ ^{} {} {}".format(
|
+ note.to_command(max_volume),
|
||||||
soundID,
|
|
||||||
mc_distance_volume,
|
|
||||||
msg[2] / 128,
|
|
||||||
mc_pitch,
|
|
||||||
),
|
|
||||||
annotation="在{}播放{}%的{}音".format(
|
annotation="在{}播放{}%的{}音".format(
|
||||||
mctick2timestr(score_now),
|
mctick2timestr(score_now),
|
||||||
max_volume * 100,
|
max_volume * 100,
|
||||||
"{}:{}".format(soundID, mc_pitch),
|
"{}:{}".format(note.mc_sound_ID, note.mc_pitch),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
cmdAmount += 1
|
command_amount += 1
|
||||||
|
|
||||||
if nowTrack:
|
if this_channel:
|
||||||
self.music_command_list.extend(nowTrack)
|
self.music_command_list.extend(this_channel)
|
||||||
tracks.append(nowTrack)
|
command_channels.append(this_channel)
|
||||||
|
|
||||||
# print(cmdAmount)
|
self.music_tick_num = max_score
|
||||||
del InstID
|
return (command_channels, command_amount, max_score)
|
||||||
self.music_tick_num = maxScore
|
|
||||||
return (tracks, cmdAmount, maxScore)
|
|
||||||
|
|
||||||
def to_command_list_in_delay(
|
def to_command_list_in_delay(
|
||||||
self,
|
self,
|
||||||
@ -720,96 +672,40 @@ class MidiConvert:
|
|||||||
raise ZeroSpeedError("播放速度仅可为正实数")
|
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||||
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
||||||
|
|
||||||
self.to_music_channels()
|
notes_list: List[SingleNote] = []
|
||||||
|
|
||||||
tracks = {}
|
|
||||||
InstID = -1
|
|
||||||
# cmd_amount = 0
|
|
||||||
|
|
||||||
# 此处 我们把通道视为音轨
|
# 此处 我们把通道视为音轨
|
||||||
for i in self.channels.keys():
|
for channel in self.to_music_note_channels().values():
|
||||||
# 如果当前通道为空 则跳过
|
notes_list.extend(channel)
|
||||||
if not self.channels[i]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 第十通道是打击乐通道
|
notes_list.sort(key=lambda a: a.start_time)
|
||||||
SpecialBits = True if i == 9 else False
|
self.music_command_list = []
|
||||||
|
multi = max_multi = 0
|
||||||
|
delaytime_previous = 0
|
||||||
|
|
||||||
# nowChannel = []
|
for note in notes_list:
|
||||||
|
delaytime_now = round(note.start_time / speed / 50)
|
||||||
for track_no, track in self.channels[i].items():
|
if (tickdelay := (delaytime_now - delaytime_previous)) == 0:
|
||||||
for msg in track:
|
multi += 1
|
||||||
if msg[0] == "PgmC":
|
else:
|
||||||
InstID = msg[1]
|
max_multi = max(max_multi, multi)
|
||||||
|
multi = 0
|
||||||
elif msg[0] == "NoteS":
|
self.music_command_list.append(
|
||||||
soundID, _X = (
|
|
||||||
self.perc_inst_to_soundID_withX(msg[1])
|
|
||||||
if SpecialBits
|
|
||||||
else self.inst_to_souldID_withX(InstID)
|
|
||||||
)
|
|
||||||
|
|
||||||
delaytime_now = round(msg[-1] / float(speed) / 50)
|
|
||||||
mc_pitch = "" if SpecialBits else 2 ** ((msg[1] - 60 - _X) / 12)
|
|
||||||
mc_distance_volume = 128 / max_volume / msg[2] + (
|
|
||||||
1 if SpecialBits else -1
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
tracks[delaytime_now].append(
|
|
||||||
self.execute_cmd_head.format(player_selector)
|
|
||||||
+ "playsound {} @s ^ ^ ^{} {} {}".format(
|
|
||||||
soundID,
|
|
||||||
mc_distance_volume,
|
|
||||||
msg[2] / 128,
|
|
||||||
mc_pitch,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except KeyError:
|
|
||||||
tracks[delaytime_now] = [
|
|
||||||
self.execute_cmd_head.format(player_selector)
|
|
||||||
+ "playsound {} @s ^ ^ ^{} {} {}".format(
|
|
||||||
soundID,
|
|
||||||
mc_distance_volume,
|
|
||||||
msg[2] / 128,
|
|
||||||
mc_pitch,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
# cmd_amount += 1
|
|
||||||
|
|
||||||
# print(cmd_amount)
|
|
||||||
|
|
||||||
del InstID
|
|
||||||
all_ticks = list(tracks.keys())
|
|
||||||
all_ticks.sort()
|
|
||||||
results = []
|
|
||||||
max_multi = 0
|
|
||||||
|
|
||||||
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]])):
|
|
||||||
results.append(
|
|
||||||
SingleCommand(
|
SingleCommand(
|
||||||
tracks[all_ticks[i]][j],
|
self.execute_cmd_head.format(player_selector)
|
||||||
tick_delay=(
|
+ note.to_command(max_volume),
|
||||||
0
|
tick_delay=tickdelay,
|
||||||
if j != 0
|
|
||||||
else (
|
|
||||||
all_ticks[i] - all_ticks[i - 1]
|
|
||||||
if i != 0
|
|
||||||
else all_ticks[i]
|
|
||||||
)
|
|
||||||
),
|
|
||||||
annotation="在{}播放{}%的{}音".format(
|
annotation="在{}播放{}%的{}音".format(
|
||||||
mctick2timestr(i), max_volume * 100, ""
|
mctick2timestr(delaytime_now),
|
||||||
|
max_volume * 100,
|
||||||
|
"{}:{}".format(note.mc_sound_ID, note.mc_pitch),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
delaytime_previous = delaytime_now
|
||||||
|
|
||||||
self.music_command_list = results
|
self.music_tick_num = round(notes_list[-1].start_time / speed / 50)
|
||||||
self.music_tick_num = max(all_ticks)
|
return self.music_command_list, self.music_tick_num, max_multi + 1
|
||||||
return results, self.music_tick_num, max_multi
|
|
||||||
|
|
||||||
def copy_important(self):
|
def copy_important(self):
|
||||||
dst = MidiConvert(
|
dst = MidiConvert(
|
||||||
|
@ -22,11 +22,11 @@ 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 (
|
from ..mcstructure import (
|
||||||
commands_to_structure,
|
|
||||||
form_command_block_in_NBT_struct,
|
|
||||||
commands_to_redstone_delay_structure,
|
|
||||||
COMPABILITY_VERSION_117,
|
COMPABILITY_VERSION_117,
|
||||||
COMPABILITY_VERSION_119,
|
COMPABILITY_VERSION_119,
|
||||||
|
commands_to_redstone_delay_structure,
|
||||||
|
commands_to_structure,
|
||||||
|
form_command_block_in_NBT_struct,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -393,7 +393,7 @@ def to_addon_pack_in_repeater(
|
|||||||
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} 音乐播放包,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"无 - 由 音·创 生成",
|
||||||
),
|
),
|
||||||
|
@ -17,11 +17,11 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
import datetime
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import zipfile
|
import zipfile
|
||||||
import datetime
|
from typing import List, Literal, Union
|
||||||
from typing import List, Union, Literal
|
|
||||||
|
|
||||||
|
|
||||||
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
|
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
|
||||||
|
@ -14,11 +14,7 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ["to_BDX_file_in_score", "to_BDX_file_in_delay"]
|
||||||
"to_BDX_file_in_score",
|
|
||||||
"to_BDX_file_in_delay"
|
|
||||||
]
|
|
||||||
__author__ = (("金羿", "Eilles Wan"),)
|
__author__ = (("金羿", "Eilles Wan"),)
|
||||||
|
|
||||||
from .main import to_BDX_file_in_delay,to_BDX_file_in_score
|
from .main import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||||
|
|
||||||
|
@ -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, Literal
|
from typing import Literal, Tuple, Union
|
||||||
|
|
||||||
from ..constants import DEFAULT_PROGRESSBAR_STYLE
|
from ..constants import DEFAULT_PROGRESSBAR_STYLE
|
||||||
|
|
||||||
|
@ -20,5 +20,4 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
__author__ = (("金羿", "Eilles Wan"),)
|
__author__ = (("金羿", "Eilles Wan"),)
|
||||||
|
|
||||||
from .main import to_mcstructure_file_in_delay,to_mcstructure_file_in_repeater
|
from .main import to_mcstructure_file_in_delay, to_mcstructure_file_in_repeater
|
||||||
|
|
||||||
|
@ -12,17 +12,16 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from typing import Literal
|
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 (
|
from ..mcstructure import (
|
||||||
commands_to_structure,
|
|
||||||
commands_to_redstone_delay_structure,
|
|
||||||
COMPABILITY_VERSION_119,
|
|
||||||
COMPABILITY_VERSION_117,
|
COMPABILITY_VERSION_117,
|
||||||
|
COMPABILITY_VERSION_119,
|
||||||
|
commands_to_redstone_delay_structure,
|
||||||
|
commands_to_structure,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -351,23 +351,26 @@ def commands_to_redstone_delay_structure(
|
|||||||
|
|
||||||
command_actually_length = sum([int(bool(cmd.delay)) for cmd in commands])
|
command_actually_length = sum([int(bool(cmd.delay)) for cmd in commands])
|
||||||
|
|
||||||
# a = 1
|
a = 1
|
||||||
# for cmd in commands:
|
a_max = 0
|
||||||
# # print("\r 正在进行处理:",end="")
|
total_cmd = 0
|
||||||
# if cmd.delay > 2:
|
for cmd in commands:
|
||||||
# a = 1
|
# print("\r 正在进行处理:",end="")
|
||||||
# else:
|
if cmd.delay > 2:
|
||||||
# a += 1
|
a_max = max(a,a_max)
|
||||||
|
total_cmd += (a := 1)
|
||||||
|
else:
|
||||||
|
a += 1
|
||||||
|
|
||||||
struct = Structure(
|
struct = Structure(
|
||||||
size=(
|
size=(
|
||||||
round(delay_length / 2 + command_actually_length)
|
round(delay_length / 2 + total_cmd)
|
||||||
if extensioon_direction == x
|
if extensioon_direction == x
|
||||||
else max_multicmd_length,
|
else a_max,
|
||||||
3,
|
3,
|
||||||
round(delay_length / 2 + command_actually_length)
|
round(delay_length / 2 + total_cmd)
|
||||||
if extensioon_direction == z
|
if extensioon_direction == z
|
||||||
else max_multicmd_length,
|
else a_max,
|
||||||
),
|
),
|
||||||
fill=Block("minecraft", "air", compability_version=compability_version_),
|
fill=Block("minecraft", "air", compability_version=compability_version_),
|
||||||
compability_version=compability_version_,
|
compability_version=compability_version_,
|
||||||
|
@ -19,6 +19,7 @@ Terms & Conditions: License.md in the root directory
|
|||||||
from ..exceptions import NotDefineProgramError, ZeroSpeedError
|
from ..exceptions import NotDefineProgramError, ZeroSpeedError
|
||||||
from ..main import MidiConvert
|
from ..main import MidiConvert
|
||||||
from ..subclass import SingleCommand
|
from ..subclass import SingleCommand
|
||||||
|
from ..utils import inst_to_souldID_withX, perc_inst_to_soundID_withX
|
||||||
|
|
||||||
# 你以为写完了吗?其实并没有
|
# 你以为写完了吗?其实并没有
|
||||||
|
|
||||||
@ -68,15 +69,15 @@ def to_note_list(
|
|||||||
elif msg[0] == "NoteS":
|
elif msg[0] == "NoteS":
|
||||||
try:
|
try:
|
||||||
soundID, _X = (
|
soundID, _X = (
|
||||||
midi_cvt.perc_inst_to_soundID_withX(InstID)
|
perc_inst_to_soundID_withX(InstID)
|
||||||
if SpecialBits
|
if SpecialBits
|
||||||
else midi_cvt.inst_to_souldID_withX(InstID)
|
else inst_to_souldID_withX(InstID)
|
||||||
)
|
)
|
||||||
except UnboundLocalError as E:
|
except UnboundLocalError as E:
|
||||||
soundID, _X = (
|
soundID, _X = (
|
||||||
midi_cvt.perc_inst_to_soundID_withX(-1)
|
perc_inst_to_soundID_withX(-1)
|
||||||
if SpecialBits
|
if SpecialBits
|
||||||
else midi_cvt.inst_to_souldID_withX(-1)
|
else inst_to_souldID_withX(-1)
|
||||||
)
|
)
|
||||||
score_now = round(msg[-1] / float(speed) / 50)
|
score_now = round(msg[-1] / float(speed) / 50)
|
||||||
# print(score_now)
|
# print(score_now)
|
||||||
|
@ -18,6 +18,7 @@ Terms & Conditions: License.md in the root directory
|
|||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .main import MidiConvert, mido
|
from .main import MidiConvert, mido
|
||||||
from .subclass import *
|
from .subclass import *
|
||||||
|
from .types import ChannelType
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
||||||
|
|
||||||
@ -27,6 +28,82 @@ class ObsoleteMidiConvert(MidiConvert):
|
|||||||
这些破烂老代码能跑得起来就是谢天谢地,你们还指望我怎么样?这玩意真的不会再维护了,我发誓!
|
这些破烂老代码能跑得起来就是谢天谢地,你们还指望我怎么样?这玩意真的不会再维护了,我发誓!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def to_music_channels(
|
||||||
|
self,
|
||||||
|
) -> ChannelType:
|
||||||
|
"""
|
||||||
|
使用金羿的转换思路,将midi解析并转换为频道信息字典
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
以频道作为分割的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_channels: ChannelType = empty_midi_channels()
|
||||||
|
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||||
|
|
||||||
|
# 我们来用通道统计音乐信息
|
||||||
|
# 但是是用分轨的思路的
|
||||||
|
for track_no, track in enumerate(self.midi.tracks):
|
||||||
|
microseconds = 0
|
||||||
|
if not track:
|
||||||
|
continue
|
||||||
|
|
||||||
|
note_queue = empty_midi_channels(staff=[])
|
||||||
|
|
||||||
|
for msg in track:
|
||||||
|
if msg.time != 0:
|
||||||
|
microseconds += msg.time * tempo / self.midi.ticks_per_beat / 1000
|
||||||
|
|
||||||
|
if msg.is_meta:
|
||||||
|
if msg.type == "set_tempo":
|
||||||
|
tempo = msg.tempo
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if not track_no in midi_channels[msg.channel].keys():
|
||||||
|
midi_channels[msg.channel][track_no] = []
|
||||||
|
except AttributeError as E:
|
||||||
|
print(msg, E)
|
||||||
|
|
||||||
|
if msg.type == "program_change":
|
||||||
|
midi_channels[msg.channel][track_no].append(
|
||||||
|
("PgmC", msg.program, microseconds)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif msg.type == "note_on" and msg.velocity != 0:
|
||||||
|
midi_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"
|
||||||
|
):
|
||||||
|
midi_channels[msg.channel][track_no].append(
|
||||||
|
("NoteE", msg.note, microseconds)
|
||||||
|
)
|
||||||
|
|
||||||
|
"""整合后的音乐通道格式
|
||||||
|
每个通道包括若干消息元素其中逃不过这三种:
|
||||||
|
|
||||||
|
1 切换乐器消息
|
||||||
|
("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
|
||||||
|
|
||||||
|
2 音符开始消息
|
||||||
|
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
|
||||||
|
|
||||||
|
3 音符结束消息
|
||||||
|
("NoteE", 结束的音符ID, 距离演奏开始的毫秒)"""
|
||||||
|
del tempo, self.channels
|
||||||
|
self.channels = midi_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_method1(
|
def to_command_list_method1(
|
||||||
self,
|
self,
|
||||||
scoreboard_name: str = "mscplay",
|
scoreboard_name: str = "mscplay",
|
||||||
@ -76,9 +153,9 @@ class ObsoleteMidiConvert(MidiConvert):
|
|||||||
)
|
)
|
||||||
maxscore = max(maxscore, nowscore)
|
maxscore = max(maxscore, nowscore)
|
||||||
if msg.channel == 9:
|
if msg.channel == 9:
|
||||||
soundID, _X = self.perc_inst_to_soundID_withX(instrumentID)
|
soundID, _X = perc_inst_to_soundID_withX(instrumentID)
|
||||||
else:
|
else:
|
||||||
soundID, _X = self.inst_to_souldID_withX(instrumentID)
|
soundID, _X = inst_to_souldID_withX(instrumentID)
|
||||||
|
|
||||||
singleTrack.append(
|
singleTrack.append(
|
||||||
"execute @a[scores={"
|
"execute @a[scores={"
|
||||||
@ -135,7 +212,7 @@ class ObsoleteMidiConvert(MidiConvert):
|
|||||||
(ticks * tempo) / ((self.midi.ticks_per_beat * float(speed)) * 50000) # type: ignore
|
(ticks * tempo) / ((self.midi.ticks_per_beat * float(speed)) * 50000) # type: ignore
|
||||||
)
|
)
|
||||||
maxscore = max(maxscore, nowscore)
|
maxscore = max(maxscore, nowscore)
|
||||||
soundID, _X = self.inst_to_souldID_withX(instrumentID)
|
soundID, _X = inst_to_souldID_withX(instrumentID)
|
||||||
singleTrack.append(
|
singleTrack.append(
|
||||||
"execute @a[scores={"
|
"execute @a[scores={"
|
||||||
+ str(scoreboardname)
|
+ str(scoreboardname)
|
||||||
@ -152,7 +229,7 @@ class ObsoleteMidiConvert(MidiConvert):
|
|||||||
|
|
||||||
# 原本这个算法的转换效果应该和上面的算法相似的
|
# 原本这个算法的转换效果应该和上面的算法相似的
|
||||||
def _toCmdList_m2(
|
def _toCmdList_m2(
|
||||||
self: MidiConvert,
|
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,
|
||||||
@ -189,16 +266,16 @@ class ObsoleteMidiConvert(MidiConvert):
|
|||||||
|
|
||||||
nowTrack = []
|
nowTrack = []
|
||||||
|
|
||||||
for track_no, track in self.channels[i].items():
|
for track_no, track in self.channels[i].items(): # type: ignore
|
||||||
for msg in track:
|
for msg in track:
|
||||||
if msg[0] == "PgmC":
|
if msg[0] == "PgmC":
|
||||||
InstID = msg[1]
|
InstID = msg[1]
|
||||||
|
|
||||||
elif msg[0] == "NoteS":
|
elif msg[0] == "NoteS":
|
||||||
soundID, _X = (
|
soundID, _X = (
|
||||||
self.perc_inst_to_soundID_withX(msg[1])
|
perc_inst_to_soundID_withX(msg[1])
|
||||||
if SpecialBits
|
if SpecialBits
|
||||||
else self.inst_to_souldID_withX(InstID)
|
else inst_to_souldID_withX(InstID)
|
||||||
)
|
)
|
||||||
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)
|
||||||
@ -221,7 +298,7 @@ class ObsoleteMidiConvert(MidiConvert):
|
|||||||
return tracks, cmdAmount, maxScore
|
return tracks, cmdAmount, maxScore
|
||||||
|
|
||||||
def _toCmdList_withDelay_m1(
|
def _toCmdList_withDelay_m1(
|
||||||
self: MidiConvert,
|
self,
|
||||||
MaxVolume: float = 1.0,
|
MaxVolume: float = 1.0,
|
||||||
speed: float = 1.0,
|
speed: float = 1.0,
|
||||||
player: str = "@a",
|
player: str = "@a",
|
||||||
@ -262,7 +339,7 @@ class ObsoleteMidiConvert(MidiConvert):
|
|||||||
(ticks * tempo)
|
(ticks * tempo)
|
||||||
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
|
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
|
||||||
)
|
)
|
||||||
soundID, _X = self.inst_to_souldID_withX(instrumentID)
|
soundID, _X = inst_to_souldID_withX(instrumentID)
|
||||||
try:
|
try:
|
||||||
tracks[now_tick].append(
|
tracks[now_tick].append(
|
||||||
self.execute_cmd_head.format(player)
|
self.execute_cmd_head.format(player)
|
||||||
@ -297,7 +374,7 @@ class ObsoleteMidiConvert(MidiConvert):
|
|||||||
return [results, max(all_ticks)]
|
return [results, max(all_ticks)]
|
||||||
|
|
||||||
def _toCmdList_withDelay_m2(
|
def _toCmdList_withDelay_m2(
|
||||||
self: MidiConvert,
|
self,
|
||||||
MaxVolume: float = 1.0,
|
MaxVolume: float = 1.0,
|
||||||
speed: float = 1.0,
|
speed: float = 1.0,
|
||||||
player: str = "@a",
|
player: str = "@a",
|
||||||
@ -329,16 +406,16 @@ class ObsoleteMidiConvert(MidiConvert):
|
|||||||
else:
|
else:
|
||||||
SpecialBits = False
|
SpecialBits = False
|
||||||
|
|
||||||
for track_no, track in self.channels[i].items():
|
for track_no, track in self.channels[i].items(): # type: ignore
|
||||||
for msg in track:
|
for msg in track:
|
||||||
if msg[0] == "PgmC":
|
if msg[0] == "PgmC":
|
||||||
InstID = msg[1]
|
InstID = msg[1]
|
||||||
|
|
||||||
elif msg[0] == "NoteS":
|
elif msg[0] == "NoteS":
|
||||||
soundID, _X = (
|
soundID, _X = (
|
||||||
self.perc_inst_to_soundID_withX(msg[1])
|
perc_inst_to_soundID_withX(msg[1])
|
||||||
if SpecialBits
|
if SpecialBits
|
||||||
else self.inst_to_souldID_withX(InstID)
|
else inst_to_souldID_withX(InstID)
|
||||||
)
|
)
|
||||||
score_now = round(msg[-1] / float(speed) / 50)
|
score_now = round(msg[-1] / float(speed) / 50)
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ from dataclasses import dataclass
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .constants import PERCUSSION_INSTRUMENT_LIST
|
from .constants import PERCUSSION_INSTRUMENT_LIST
|
||||||
|
from .utils import inst_to_souldID_withX, perc_inst_to_soundID_withX
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
@ -45,14 +46,18 @@ class SingleNote:
|
|||||||
track_no: int
|
track_no: int
|
||||||
"""音符所处的音轨"""
|
"""音符所处的音轨"""
|
||||||
|
|
||||||
|
percussive: bool
|
||||||
|
"""是否为打击乐器"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
instrument: int,
|
instrument: int,
|
||||||
pitch: int,
|
pitch: int,
|
||||||
velocity: int,
|
velocity: int,
|
||||||
startTime: int,
|
startime: int,
|
||||||
lastTime: int,
|
lastime: int,
|
||||||
track_number: int = 0,
|
track_number: int = 0,
|
||||||
|
is_percussion: Optional[bool] = None,
|
||||||
):
|
):
|
||||||
"""用于存储单个音符的类
|
"""用于存储单个音符的类
|
||||||
:param instrument 乐器编号
|
:param instrument 乐器编号
|
||||||
@ -67,12 +72,21 @@ class SingleNote:
|
|||||||
"""音符编号"""
|
"""音符编号"""
|
||||||
self.velocity: int = velocity
|
self.velocity: int = velocity
|
||||||
"""力度/响度"""
|
"""力度/响度"""
|
||||||
self.start_time: int = startTime
|
self.start_time: int = startime
|
||||||
"""开始之时 ms"""
|
"""开始之时 ms"""
|
||||||
self.duration: int = lastTime
|
self.duration: int = lastime
|
||||||
"""音符持续时间 ms"""
|
"""音符持续时间 ms"""
|
||||||
self.track_no: int = track_number
|
self.track_no: int = track_number
|
||||||
"""音符所处的音轨"""
|
"""音符所处的音轨"""
|
||||||
|
self.track_no: int = track_number
|
||||||
|
"""音符所处的音轨"""
|
||||||
|
|
||||||
|
self.percussive = (
|
||||||
|
(is_percussion in PERCUSSION_INSTRUMENT_LIST)
|
||||||
|
if (is_percussion is None)
|
||||||
|
else is_percussion
|
||||||
|
)
|
||||||
|
"""是否为打击乐器"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inst(self):
|
def inst(self):
|
||||||
@ -89,28 +103,73 @@ class SingleNote:
|
|||||||
return self.note
|
return self.note
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (
|
return "{}Note(Instrument = {}, {}Velocity = {}, StartTime = {}, Duration = {},)".format(
|
||||||
f"Note(inst = {self.inst}, pitch = {self.note}, velocity = {self.velocity}, "
|
"Percussive" if self.percussive else "",
|
||||||
f"startTime = {self.start_time}, lastTime = {self.duration}, )"
|
self.inst,
|
||||||
|
"" if self.percussive else "Pitch = {}, ".format(self.pitch),
|
||||||
|
self.start_time,
|
||||||
|
self.duration,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __tuple__(self):
|
def __tuple__(self):
|
||||||
return self.inst, self.note, self.velocity, self.start_time, self.duration
|
return (
|
||||||
|
(self.percussive, self.inst, self.velocity, self.start_time, self.duration)
|
||||||
|
if self.percussive
|
||||||
|
else (
|
||||||
|
self.percussive,
|
||||||
|
self.inst,
|
||||||
|
self.note,
|
||||||
|
self.velocity,
|
||||||
|
self.start_time,
|
||||||
|
self.duration,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def __dict__(self):
|
def __dict__(self):
|
||||||
return {
|
return (
|
||||||
"inst": self.inst,
|
{
|
||||||
"pitch": self.note,
|
"Percussive": self.percussive,
|
||||||
"velocity": self.velocity,
|
"Instrument": self.inst,
|
||||||
"startTime": self.start_time,
|
"Velocity": self.velocity,
|
||||||
"lastTime": self.duration,
|
"StartTime": self.start_time,
|
||||||
|
"Duration": self.duration,
|
||||||
}
|
}
|
||||||
|
if self.percussive
|
||||||
|
else {
|
||||||
|
"Percussive": self.percussive,
|
||||||
|
"Instrument": self.inst,
|
||||||
|
"Pitch": self.note,
|
||||||
|
"Velocity": self.velocity,
|
||||||
|
"StartTime": self.start_time,
|
||||||
|
"Duration": self.duration,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other) -> bool:
|
||||||
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__()
|
||||||
|
|
||||||
|
def to_command(self, volume_percentage) -> str:
|
||||||
|
self.mc_sound_ID, _X = (
|
||||||
|
perc_inst_to_soundID_withX(self.inst)
|
||||||
|
if self.percussive
|
||||||
|
else inst_to_souldID_withX(self.inst)
|
||||||
|
)
|
||||||
|
|
||||||
|
# delaytime_now = round(self.start_time / float(speed) / 50)
|
||||||
|
self.mc_pitch = "" if self.percussive else 2 ** ((self.note - 60 - _X) / 12)
|
||||||
|
self.mc_distance_volume = 128 / volume_percentage / self.velocity + (
|
||||||
|
1 if self.percussive else self.velocity / 32
|
||||||
|
)
|
||||||
|
|
||||||
|
return "playsound {} @s ^ ^ ^{} {} {}".format(
|
||||||
|
self.mc_sound_ID,
|
||||||
|
self.mc_distance_volume,
|
||||||
|
self.velocity / 128,
|
||||||
|
self.mc_pitch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class SingleCommand:
|
class SingleCommand:
|
||||||
|
64
Musicreater/types.py
Normal file
64
Musicreater/types.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# -*- 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 Any, Dict, List, Literal, Optional, Tuple, Union
|
||||||
|
|
||||||
|
import mido
|
||||||
|
|
||||||
|
from .subclass import SingleNote
|
||||||
|
|
||||||
|
ProgressStyle = Tuple[str, Tuple[str, str]]
|
||||||
|
"""
|
||||||
|
进度条样式类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
VoidMido = Union[mido.MidiFile, None] # void mido
|
||||||
|
"""
|
||||||
|
空Midi类类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
NoteChannelType = Dict[
|
||||||
|
int,
|
||||||
|
List[SingleNote,],
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
频道信息类型
|
||||||
|
|
||||||
|
Dict[int,Dict[int,List[SingleNote,],],]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
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],]],],]
|
||||||
|
"""
|
@ -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 Any, Dict
|
from .constants import PERCUSSION_INSTRUMENT_TABLE, PITCHED_INSTRUMENT_TABLE
|
||||||
|
from typing import Any, Dict, Tuple
|
||||||
|
|
||||||
|
|
||||||
def mctick2timestr(mc_tick: int) -> str:
|
def mctick2timestr(mc_tick: int) -> str:
|
||||||
@ -37,3 +38,54 @@ def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, A
|
|||||||
) # 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
) # 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
||||||
for i in range(channel_count)
|
for i in range(channel_count)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def inst_to_souldID_withX(
|
||||||
|
instrumentID: int,
|
||||||
|
) -> Tuple[str, int]:
|
||||||
|
"""
|
||||||
|
返回midi的乐器ID对应的我的世界乐器名,对于音域转换算法,如下:
|
||||||
|
2**( ( msg.note - 60 - X ) / 12 ) 即为MC的音高,其中
|
||||||
|
X的取值随乐器不同而变化:
|
||||||
|
竖琴harp、电钢琴pling、班卓琴banjo、方波bit、颤音琴iron_xylophone 的时候为6
|
||||||
|
吉他的时候为7
|
||||||
|
贝斯bass、迪吉里杜管didgeridoo的时候为8
|
||||||
|
长笛flute、牛铃cou_bell的时候为5
|
||||||
|
钟琴bell、管钟chime、木琴xylophone的时候为4
|
||||||
|
而存在一些打击乐器bd(basedrum)、hat、snare,没有音域,则没有X,那么我们返回7即可
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
instrumentID: int
|
||||||
|
midi的乐器ID
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple(str我的世界乐器名, int转换算法中的X)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return PITCHED_INSTRUMENT_TABLE[instrumentID]
|
||||||
|
except KeyError:
|
||||||
|
return "note.flute", 5
|
||||||
|
|
||||||
|
|
||||||
|
def perc_inst_to_soundID_withX(instrumentID: int) -> Tuple[str, int]:
|
||||||
|
"""
|
||||||
|
对于Midi第10通道所对应的打击乐器,返回我的世界乐器名
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
instrumentID: int
|
||||||
|
midi的乐器ID
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple(str我的世界乐器名, int转换算法中的X)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return PERCUSSION_INSTRUMENT_TABLE[instrumentID]
|
||||||
|
except KeyError:
|
||||||
|
return "note.bd", 7
|
||||||
|
|
||||||
|
# 明明已经走了
|
||||||
|
# 凭什么还要在我心里留下缠绵缱绻
|
||||||
|
10
example.py
10
example.py
@ -20,12 +20,12 @@ import os
|
|||||||
|
|
||||||
import Musicreater
|
import Musicreater
|
||||||
from Musicreater.plugin import ConvertConfig
|
from Musicreater.plugin import ConvertConfig
|
||||||
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
|
||||||
from Musicreater.plugin.addonpack import (
|
from Musicreater.plugin.addonpack import (
|
||||||
to_addon_pack_in_delay,
|
to_addon_pack_in_delay,
|
||||||
to_addon_pack_in_repeater,
|
to_addon_pack_in_repeater,
|
||||||
to_addon_pack_in_score,
|
to_addon_pack_in_score,
|
||||||
)
|
)
|
||||||
|
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||||
|
|
||||||
# 获取midi列表
|
# 获取midi列表
|
||||||
midi_path = input(f"请输入MIDI路径:")
|
midi_path = input(f"请输入MIDI路径:")
|
||||||
@ -41,13 +41,13 @@ playerFormat = int(input(f"请选择播放方式[红石(2) 或 计分板(1) 或
|
|||||||
|
|
||||||
|
|
||||||
# 真假字符串判断
|
# 真假字符串判断
|
||||||
def bool_str(sth: str) -> bool:
|
def bool_str(sth: str):
|
||||||
try:
|
try:
|
||||||
return bool(float(sth))
|
return bool(float(sth))
|
||||||
except ValueError:
|
except:
|
||||||
if str(sth).lower() == "true":
|
if str(sth).lower() in ("true", "真", "是", "y", "t"):
|
||||||
return True
|
return True
|
||||||
elif str(sth).lower() == "false":
|
elif str(sth).lower() in ("false", "假", "否", "f", "n"):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise ValueError("布尔字符串啊?")
|
raise ValueError("布尔字符串啊?")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user