2023-01-06 12:32:18 +08:00
|
|
|
|
<h1 align="center">音·创 Musicreater</h1>
|
|
|
|
|
|
|
|
|
|
<h2 align="center">库版 Package Version</h2>
|
|
|
|
|
|
|
|
|
|
<p align="center">
|
|
|
|
|
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
|
|
|
|
|
</p>
|
|
|
|
|
|
2023-01-20 01:03:59 +08:00
|
|
|
|
**此为开发相关文档,内容包括:所生成文件结构的详细说明、特殊参数的详细解释**
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|
2023-01-20 21:34:15 +08:00
|
|
|
|
# 库的简单调用
|
|
|
|
|
|
2023-05-27 18:41:02 +08:00
|
|
|
|
参见[example.py的相关部分](../example.py),使用此库进行MIDI转换非常简单。
|
|
|
|
|
|
|
|
|
|
- 在导入转换库后,使用 MidiConvert 类建立转换对象(读取Midi文件)
|
|
|
|
|
|
|
|
|
|
音·创库支持新旧两种execute语法,需要在对象实例化时指定
|
|
|
|
|
```python
|
|
|
|
|
# 导入音·创库
|
|
|
|
|
import Musicreater
|
|
|
|
|
|
|
|
|
|
# 指定是否使用旧的execute指令语法(即1.18及以前的《我的世界:基岩版》语法)
|
|
|
|
|
old_execute_format = False
|
|
|
|
|
|
|
|
|
|
# 可以通过文件地址自动读取
|
|
|
|
|
cvt_mid = Musicreater.MidiConvert.from_midi_file(
|
|
|
|
|
"Midi文件地址",
|
|
|
|
|
old_exe_format=old_execute_format
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 也可以导入Mido对象
|
|
|
|
|
cvt_mid = Musicreater.MidiConvert(
|
|
|
|
|
mido.MidiFile("Midi文件地址"),
|
|
|
|
|
"音乐名称",
|
|
|
|
|
old_exe_format=old_execute_format
|
|
|
|
|
)
|
|
|
|
|
```
|
2023-01-20 21:34:15 +08:00
|
|
|
|
|
2023-05-27 18:41:02 +08:00
|
|
|
|
- 获取 Midi 音乐经转换后的播放指令
|
2023-01-20 21:34:15 +08:00
|
|
|
|
|
2023-05-27 18:41:02 +08:00
|
|
|
|
```python
|
|
|
|
|
# 通过函数 to_command_list_in_score, to_command_list_in_delay
|
|
|
|
|
# 分别可以得到
|
|
|
|
|
# 以计分板作为播放器的指令对象列表、以延迟作为播放器的指令对象列表
|
|
|
|
|
# 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
|
|
|
|
|
|
|
|
|
|
# 使用 to_command_list_in_score 函数进行转换之后,返回值有三个
|
|
|
|
|
# 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表
|
|
|
|
|
# 也就是列表套列表
|
|
|
|
|
# 但是,在对象内部所存储的数据却不会如此嵌套
|
|
|
|
|
command_channel_list, command_count, max_score = cvt_mid.to_command_list_in_score(
|
|
|
|
|
"计分板名称",
|
|
|
|
|
1.0, # 音量比率
|
|
|
|
|
1.0, # 速度倍率
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 使用 to_command_list_in_delay 转换后的返回值只有两个
|
|
|
|
|
# 但是第一个返回值没有列表套列表
|
|
|
|
|
command_list, max_delay = cvt_mid.to_command_list_in_delay(
|
|
|
|
|
1.0, # 音量比率
|
|
|
|
|
1.0, # 速度倍率
|
|
|
|
|
"@a", # 玩家选择器
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 运行之后,指令和总延迟会存储至对象内
|
|
|
|
|
print(
|
|
|
|
|
"音乐长度:{}/游戏刻".format(
|
|
|
|
|
cvt_mid.music_tick_num
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
print(
|
|
|
|
|
"指令如下:\n{}".format(
|
|
|
|
|
cvt_mid.music_command_list
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
```
|
2023-04-29 17:00:32 +08:00
|
|
|
|
|
2023-05-27 18:41:02 +08:00
|
|
|
|
- 除了获取播放指令外,还可以获取进度条指令
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# 通过函数 form_progress_bar 可以获得
|
|
|
|
|
# 以计分板为载体所生成的进度条的指令对象列表
|
|
|
|
|
# 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
|
|
|
|
|
|
|
|
|
|
# 使用 form_progress_bar 函数进行转换之后,返回值有三个
|
|
|
|
|
# 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表
|
|
|
|
|
# 也就是列表套列表
|
|
|
|
|
cvt_mid.form_progress_bar(
|
|
|
|
|
max_score, # 音乐时长游戏刻
|
|
|
|
|
scoreboard_name, # 进度条使用的计分板名称
|
|
|
|
|
progressbar_style, # 进度条样式组(详见下方)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 同上面生成播放指令的理,进度条指令也混存储至对象内
|
|
|
|
|
print(
|
|
|
|
|
"进度条指令如下:\n{}".format(
|
|
|
|
|
cvt_mid.progress_bar_command
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在上面的代码中,进度条样式是可以自定义的,详见[下方说明](%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md#进度条自定义)。
|
|
|
|
|
|
|
|
|
|
- 转换成指令是一个方面,接下来是再转换为可以导入MC的格式
|
|
|
|
|
|
|
|
|
|
***这后面一直到下一个小节的内容全是没改的旧内容,肯定有问题***
|
|
|
|
|
|
|
|
|
|
```python
|
2023-01-20 21:34:15 +08:00
|
|
|
|
|
|
|
|
|
# 设置输入输出地址,并指定execute指令语法
|
|
|
|
|
# 地址都为字符串类型,不能传入文件流
|
|
|
|
|
midi_path = "./where/you/place/.midi/files.mid"
|
|
|
|
|
output_folder = "./where/you/want2/convert/into/"
|
2023-03-11 18:23:40 +08:00
|
|
|
|
|
|
|
|
|
# 设定基本转换参数
|
2023-04-29 17:00:32 +08:00
|
|
|
|
conversion.convert(midi_path,output_folder)
|
2023-01-20 21:34:15 +08:00
|
|
|
|
|
2023-03-11 18:23:40 +08:00
|
|
|
|
# 进行转换并接受输出,具体的参数均在代码之文档中有相关说明
|
|
|
|
|
method_id = 3 # 指定使用的转换算法
|
|
|
|
|
|
|
|
|
|
# 使用计分板播放器,转换为附加包文件
|
|
|
|
|
convertion_result = conversion.to_mcpack(method_id,*prompts)
|
|
|
|
|
|
2023-04-30 21:57:14 +08:00
|
|
|
|
# 使用延迟播放器,转换为附加包文件
|
|
|
|
|
# 注意,在执行这个功能之前,你需要使用指令
|
|
|
|
|
# pip install TrimMCStruct
|
|
|
|
|
# 安装最新版的TrimMCStruct库
|
|
|
|
|
convertion_result = conversion.to_mcpack_with_delay(method_id,*prompts)
|
|
|
|
|
|
2023-03-11 18:23:40 +08:00
|
|
|
|
# 使用计分板播放器,转换为BDX结构文件
|
|
|
|
|
convertion_result = conversion.to_BDX_file(method_id,*prompts)
|
|
|
|
|
|
|
|
|
|
# 使用延迟播放器,转换为BDX结构文件
|
|
|
|
|
convertion_result = conversion.to_BDX_file_with_delay(method_id,*prompts)
|
2023-01-20 21:34:15 +08:00
|
|
|
|
|
|
|
|
|
# 转换结果是一个元组。
|
2023-05-27 18:41:02 +08:00
|
|
|
|
# 若其转换成功,则前二位必为
|
2023-01-20 21:34:15 +08:00
|
|
|
|
# True, 指令数量, 最大延迟
|
|
|
|
|
# 其中,最大延迟可以理解为计分板的最大值
|
|
|
|
|
# 如果转换失败,暂时还没有定返回值的规则
|
|
|
|
|
# 但是有一点是肯定的,数据结构必定是元组
|
|
|
|
|
print(convertion_result)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
2023-01-20 01:03:59 +08:00
|
|
|
|
# 生成文件结构
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|
|
|
|
|
## 名词解释
|
|
|
|
|
|
|
|
|
|
|名词|解释|备注|
|
|
|
|
|
|--------|-----------|----------|
|
|
|
|
|
|指令区|一个用于放置指令系统的区域,通常是常加载区。|常见于服务器指令系统、好友联机房间中|
|
|
|
|
|
|指令链(链)|与链式指令方块不同,一个指令链通常指代的是一串由某种非链式指令方块作为开头,后面连着一串链式指令方块的结构。|通常的链都应用于需要“单次激活而多指令”的简单功能|
|
|
|
|
|
|起始块|链最初的那个非链式指令方块。|此方块为脉冲方块或重复方块皆可|
|
|
|
|
|
|指令系统(系统)|指令系统通常指的是,由一个或多个指令链以及相关红石机构相互配合、一同组成的,为达到某种特定的功能而构建的整体结构。|通常的系统都应用于需要“综合调配指令”的复杂功能。可由多个实现不同功能的模块构成,不同系统之间可以相互调用各自的模块。|
|
|
|
|
|
|游戏刻(刻)|游戏的一刻是指《我的世界》的游戏循环运行一次所占用的时间。([详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E6%B8%B8%E6%88%8F%E5%88%BB))。指令方块的延迟功能(即指令方块的“延迟刻数”设置项,此项的名称被误译为“已选中项的延迟”)的单位即为`1`游戏刻。|正常情况下,游戏固定以每秒钟20刻的速率运行。但是,由于游戏内的绝大多数操作都是基于刻数而非现实中的时间来计时并进行的,一次游戏循环内也许会发生大量的操作,很多情况下,一秒对应的游戏刻会更少。 |
|
|
|
|
|
|
|
|
|
|
## 文件格式
|
|
|
|
|
|
|
|
|
|
1. 附加包格式(`.mcpack`)
|
|
|
|
|
|
|
|
|
|
使用附加包格式导出音乐,则音乐会以指令函数文件(`.mcfunction`)存储于附加包内。在附加包中,函数文件的存储结构应为:
|
|
|
|
|
|
|
|
|
|
- `functions\`
|
|
|
|
|
- `index.mcfunction`
|
|
|
|
|
- `mscply\`
|
|
|
|
|
- `progressShow.mcfunction`
|
|
|
|
|
- `track1.mcfunction`
|
|
|
|
|
- `track2.mcfunction`
|
|
|
|
|
- ...
|
|
|
|
|
- `trackN.mcfunction`
|
|
|
|
|
|
|
|
|
|
如图,其中,`index.mcfunction`文件和`mscply`文件夹存在于函数目录的根下;在`mscply`目录中,包含音乐导出的众多音轨播放文件(`trackX.mcfunction`),同时,若生成此包时选择了带有进度条的选项,则会包含`progressShow.mcfunction`文件。
|
|
|
|
|
|
|
|
|
|
`index.mcfunction`用于开始播放,其中包含打开各个音轨对应函数的指令,以及加分指令,这里的加分,是将**播放计分板的值大于等于`1`**的所有**玩家**的播放计分板分数增加`1`。同时,若生成此包时选择了自动重置计分板的选项,则会包含一条重置计分板的指令。
|
|
|
|
|
|
|
|
|
|
> 你知道吗?音·创的最早期版本“《我的世界》函数音乐生成器”正是用函数来播放,不过这个版本采取的读入数据的形式大有不同。
|
|
|
|
|
|
|
|
|
|
2. 结构格式
|
|
|
|
|
|
|
|
|
|
无论是音·创生成的是何种结构,`MCSTRUCTURE`还是`BDX`,都会依照此处的格式来生成。此处我们想说明的结构的格式不是结构文件存储的格式,而是结构导出之后方块将如何摆放的问题。结构文件存储的格式这一点,在各个《我的世界》开发的相关网站上都可能会有说明。
|
|
|
|
|
|
|
|
|
|
考虑到进行《我的世界》游戏开发时,为了节约常加载区域,很多游戏会将指令区设立为一种层叠式的结构。这种结构会限制每一层的指令系统的高度,但是虽然长宽也是有限的,却仍然比其纵轴延伸得更加自由。
|
|
|
|
|
|
2023-04-29 17:00:32 +08:00
|
|
|
|
所以,结构的生成形状依照给定的高度和内含指令的数量决定。其 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整。用数学公式的方式表达,则是:
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|
2023-05-27 18:41:02 +08:00
|
|
|
|
$$ MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor $$
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|
2023-05-27 18:41:02 +08:00
|
|
|
|
其中,$MaxZ$ 即生成结构的$Z$轴最大延伸长度,$NoC$ 表示链结构中所含指令方块的个数,$MaxH$ 表示给定的生成结构的最大高度。
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|
2023-05-27 18:41:02 +08:00
|
|
|
|
我们的结构生成器在生成指令链时,将首先以相对坐标系 $(0, 0, 0)$ (即相对原点)开始,自下向上堆叠高度轴(即 $Y$ 轴)的长,当高度轴达到了限制的高度时,便将 $Z$ 轴向正方向堆叠`1`个方块,并开始自上向下重新堆叠,直至高度轴坐标达到相对为 `0`。若当所生成结构的 $Z$ 轴长达到了其最大延伸长度,则此结构生成器将反转 $Z$ 轴的堆叠方向,直至 $Z$ 轴坐标相对为 `0`。如此往复,直至指令链堆叠完成。
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|
|
|
|
|
## 播放器
|
|
|
|
|
|
|
|
|
|
以结构生成的文件可以采用多种方式播放,一类播放方式,我们称其为**播放器**,例如**延迟播放器**和**计分板播放器**等等,以后推出的新的播放器,届时也会在此处更新。
|
|
|
|
|
|
|
|
|
|
为什么要设计这么多播放器?是为了适应不同的播放环境需要。通常情况下,一个音乐中含有多个音符,音符与音符之间存在间隔,这里就产生了不一样的,实现音符间时间间隔的方式。而不同的应用环境下,又会产生不一样的要求。接下来将对不同的播放器进行详细介绍。
|
|
|
|
|
|
|
|
|
|
1. 计分板播放器
|
|
|
|
|
|
|
|
|
|
计分板播放器是一种传统的《我的世界》音乐播放方式。通过对于计分板加分来实现播放不同的音符。一个很简单的原理,就是**用不同的计分板分值对应不同的音符**,再通过加分,来达到那个分值,即播放出来。
|
|
|
|
|
|
|
|
|
|
在**音·创**中,用来达到这种效果的指令是这样的:
|
|
|
|
|
|
|
|
|
|
```mcfunction
|
|
|
|
|
execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ~ ~Ht ~ Vlct Ptc
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|参数|说明|备注|
|
|
|
|
|
|--------|-----------|----------|
|
|
|
|
|
|`ScBd`|指定的计分板名称||
|
|
|
|
|
|`x`|音发出时对应的分数值||
|
|
|
|
|
|`InstID`|声音效果ID|不同的声音ID可以对应不同的乐器,详见转换[乐器对照表](./%E8%BD%AC%E6%8D%A2%E4%B9%90%E5%99%A8%E5%AF%B9%E7%85%A7%E8%A1%A8.md)|
|
2023-04-29 17:00:32 +08:00
|
|
|
|
|`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以 $S$ 表示此参数`Ht`,以Vol表示音量百分比,则计算公式为: $S = \frac{1}{Vol}-1$ |
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|`Vlct`|原生我的世界中规定的播放力度|这个参数是一个谜一样的存在,似乎它的值毫不重要……因为无论这个值是多少,我们听起来都差不多。当此音符所在MIDI通道为第一通道,则这个值为0.7倍MIDI指定力度,其他则为0.9倍。|
|
2023-04-29 17:00:32 +08:00
|
|
|
|
|`Ptc`|音符的音高|这是决定音调的参数。以 $P$ 表示此参数, $n$ 表示其在MIDI中的编号, $x$ 表示一定的音域偏移,则计算公式为: $P = 2^\frac{n-60-x}{12}$ |
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|
|
|
|
|
后四个参数决定了这个音的性质,而前两个参数仅仅是为了决定音播放的时间。
|
|
|
|
|
|
|
|
|
|
2. 延迟播放器
|
|
|
|
|
|
|
|
|
|
延迟播放器是通过《我的世界》游戏中,指令方块的设置项“延迟刻数”来达到定位音符的效果。**将所有的音符依照其播放时距离乐曲开始时的时间(毫秒),放在一个序列内,再计算音符两两之间对应的时间差值,转换为《我的世界》内对应的游戏刻数之后填入指令方块的设置中。**
|
|
|
|
|
|
|
|
|
|
在音·创中,由于此方式播放的音乐不需要用计分板,所以播放指令是这样的:
|
|
|
|
|
|
|
|
|
|
```mcfunction
|
|
|
|
|
execute Tg ~ ~ ~ playsound InstID @s ~ ~Ht ~ Vlct Ptc
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|参数|说明|备注|
|
|
|
|
|
|--------|-----------|----------|
|
|
|
|
|
|`Tg`|播放对象|选择器或玩家名|
|
|
|
|
|
|`InstID`|声音效果ID|不同的声音ID可以对应不同的乐器,详见转换[乐器对照表](./%E8%BD%AC%E6%8D%A2%E4%B9%90%E5%99%A8%E5%AF%B9%E7%85%A7%E8%A1%A8.md)|
|
2023-04-29 17:00:32 +08:00
|
|
|
|
|`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以 $S$ 表示此参数`Ht`,以Vol表示音量百分比,则计算公式为: $S = \frac{1}{Vol}-1$ |
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|`Vlct`|原生我的世界中规定的播放力度|这个参数是一个谜一样的存在,似乎它的值毫不重要……因为无论这个值是多少,我们听起来都差不多。当此音符所在MIDI通道为第一通道,则这个值为0.7倍MIDI指定力度,其他则为0.9倍。|
|
2023-04-29 17:00:32 +08:00
|
|
|
|
|`Ptc`|音符的音高|这是决定音调的参数。以 $P$ 表示此参数, $n$ 表示其在MIDI中的编号,$x$表示一定的音域偏移,则计算公式为: $P = 2^\frac{n-60-x}{12}$ |
|
2023-01-06 12:32:18 +08:00
|
|
|
|
|
|
|
|
|
其中后四个参数决定了这个音的性质。
|
|
|
|
|
|
2023-01-20 01:03:59 +08:00
|
|
|
|
由于这样的延迟数据是依赖于指令方块的设置项,所以使用这种播放器所转换出的结果仅可以存储在包含方块NBT信息及方块实体NBT信息的结构文件中,或者直接输出至世界。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 进度条自定义
|
|
|
|
|
|
|
|
|
|
因为我们提供了可以自动转换进度条的功能,因此在这里给出进度条自定义参数的详细解释。
|
|
|
|
|
|
|
|
|
|
请注意,并非所有的演示样例程序都支持自定义进度条。
|
|
|
|
|
|
|
|
|
|
一个进度条,明显地,有**固定部分**和**可变部分**来构成。而可变部分又包括了文字和图形两种(当然,《我的世界》里头的进度条,可变的图形也就是那个“条”了)。这一点你需要了解,因为后文中包含了很多这方面的概念需要你了解。
|
|
|
|
|
|
|
|
|
|
进度条的自定义功能使用一个字符串来定义自己的样式,其中包含众多**标识符**来表示可变部分。
|
|
|
|
|
|
|
|
|
|
标识符如下(注意大小写):
|
|
|
|
|
|
|
|
|
|
| 标识符 | 指定的可变量 |
|
|
|
|
|
|---------|----------------|
|
|
|
|
|
| `%%N` | 乐曲名(即传入的文件名)|
|
|
|
|
|
| `%%s` | 当前计分板值 |
|
|
|
|
|
| `%^s` | 计分板最大值 |
|
|
|
|
|
| `%%t` | 当前播放时间 |
|
|
|
|
|
| `%^t` | 曲目总时长 |
|
|
|
|
|
| `%%%` | 当前进度比率 |
|
|
|
|
|
| `_` | 用以表示进度条占位|
|
|
|
|
|
|
|
|
|
|
表示进度条占位的 `_` 是用来标识你的进度条的。也就是可变部分的唯一的图形部分。
|
|
|
|
|
|
|
|
|
|
**样式定义字符串**的样例如下,这也是默认的进度条的样式:
|
|
|
|
|
|
|
|
|
|
`▶ %%N [ %%s/%^s %%% __________ %%t|%^t]`
|
|
|
|
|
|
|
|
|
|
这是单独一行的进度条,当然你也可以制作多行的,如果是一行的,输出时所使用的指令便是 `title`,而如果是多行的话,输出就会用 `titleraw` 作为进度条字幕。
|
|
|
|
|
|
|
|
|
|
哦对了,上面的只不过是样式定义,同时还需要定义的是可变图形的部分,也就是进度条上那个真正的“条”。
|
|
|
|
|
|
|
|
|
|
对于这个我们就采用了固定参数的方法,对于一个进度条,无非就是“已经播放过的”和“没播放过的”两种形态,所以,使用一个元组来传入这两个参数就是最简单的了。元组的格式也很简单:`(str: 播放过的部分长啥样, str: 没播放过的部分长啥样)` 。例如,我们默认的进度“条”的定义是这样的:
|
|
|
|
|
|
|
|
|
|
`('§e=§r', '§7=§r')`
|
|
|
|
|
|
|
|
|
|
综合起来,把这些参数传给函数需要一个参数整合,你猜用的啥?啊对对对,我用的还是元组!
|
|
|
|
|
|
|
|
|
|
我们的默认定义参数如下:
|
|
|
|
|
|
|
|
|
|
`(r'▶ %%N [ %%s/%^s %%% __________ %%t|%^t]',('§e=§r', '§7=§r'))`
|
|
|
|
|
|
2023-05-27 18:41:02 +08:00
|
|
|
|
*为了避免生成错误,请尽量避免使用标识符作为定义样式字符串的其他部分*
|
2023-01-20 01:03:59 +08:00
|
|
|
|
|