<h1 align="center">音·创 Musicreater</h1>

<p align="center">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
</p>

**此为开发相关文档，内容包括：库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释**

# 库的简单调用

参见[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
	)
	```

-	获取 Midi 音乐经转换后的播放指令

	```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
		)
	)
	```

-	除了获取播放指令外，还可以获取进度条指令

	```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的格式。我们提供了 **音·创** 内置的附加组件，可以借助 `MidiConvert` 对象转换为相应格式。

	```python
	# 导入 Musicreater
	import Musicreater
	# 导入附加组件功能
	import Musicreater.plugin


	# 导入相应的文件格式转换功能

	# 转换为函数附加包
	import Musicreater.plugin.funpack
	# 转换为 BDX 结构文件
	import Musicreater.plugin.bdxfile
	# 转换为 mcstructure 结构文件
	import Musicreater.plugin.mcstructfile
	# 转换为结构附加包
	import Musicreater.plugin.mcstructpack
	# 直接通过 websocket 功能播放（正在开发）
	import Musicreater.plugin.websocket


	# 定义转换参数
	cvt_cfg = Musicreater.plugin.ConvertConfig(
		output_path,
		volumn,			# 音量大小参数
		speed,			# 速度倍率
		progressbar,	# 进度条样式组（详见下方）
	)

	# 使用附加组件转换，其调用的函数应为：
	# Musicreater.plugin.输出格式.播放器格式
	# 值得注意的是，并非所有输出格式都支持所有播放器格式
	# 调用的时候还请注意甄别
	# 例如，以下函数是将 MidiConvert 对象 cvt_mid
	# 以 cvt_cfg 指定的参数
	# 以延迟播放器转换为 mcstructure 文件
	Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
		cvt_mid,
		cvt_cfg,
	)
	```


#	生成文件结构

##	名词解释

|名词|解释|备注|
|--------|-----------|----------|
|指令区|一个用于放置指令系统的区域，通常是常加载区。|常见于服务器指令系统、好友联机房间中|
|指令链（链）|与链式指令方块不同，一个指令链通常指代的是一串由某种非链式指令方块作为开头，后面连着一串链式指令方块的结构。|通常的链都应用于需要“单次激活而多指令”的简单功能|
|起始块|链最初的那个非链式指令方块。|此方块为脉冲方块或重复方块皆可|
|指令系统（系统）|指令系统通常指的是，由一个或多个指令链以及相关红石机构相互配合、一同组成的，为达到某种特定的功能而构建的整体结构。|通常的系统都应用于需要“综合调配指令”的复杂功能。可由多个实现不同功能的模块构成，不同系统之间可以相互调用各自的模块。|
|游戏刻（刻）|游戏的一刻是指《我的世界》的游戏进程循环运行一次所占用的时间。（[详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E6%B8%B8%E6%88%8F%E5%88%BB)）。指令方块的延迟功能（即指令方块的“延迟刻数”设置项，此项的名称被误译为“已选中项的延迟”）的单位即为`1`游戏刻。|正常情况下，游戏固定以每秒钟 $20$ 刻的速率运行。但是，由于游戏内的绝大多数操作都是基于游戏进程循环而非现实中的时间来计时并进行的，一次游戏循环内也许会发生大量的操作，更多情况下，一秒对应的游戏刻会更少。|
|红石刻|一个红石刻代表了两个游戏刻。（[详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E7%BA%A2%E7%9F%B3%E5%88%BB)）。红石中继器会带来 $1$~$4$ 个红石刻的延迟，其默认的延迟时间为 $1$ 红石刻。|正常情况下，红石信号在一个红石电路中传输回存在 $\frac{1}{10}$ 秒左右的延迟。但是，同理于游戏刻，一秒对应的红石刻是不定的。|

##	播放器

**音·创**生成的文件可以采用多种方式播放，一类播放方式，我们称其为**播放器**，例如**延迟播放器**和**计分板播放器**等等，以后推出的新的播放器，届时也会在此处更新。

为什么要设计这么多播放器？是为了适应不同的播放环境需要。通常情况下，一个音乐中含有多个音符，音符与音符之间存在间隔，这里就产生了不一样的，实现音符间时间间隔的方式。而不同的应用环境下，又会产生不一样的要求。接下来将对不同的播放器进行详细介绍。

### 参数释义

|参数|说明|备注|
|--------|-----------|----------|
|`ScBd`|指定的计分板名称||
|`Tg`|播放对象|选择器或玩家名|
|`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)|
|`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以 $S$ 表示此参数`Ht`，以Vol表示音量百分比，则计算公式为： $S = \frac{1}{Vol}-1$ |
|`Vlct`|原生我的世界中规定的播放力度|这个参数是一个谜一样的存在，似乎它的值毫不重要……因为无论这个值是多少，我们听起来都差不多。当此音符所在MIDI通道为第一通道，则这个值为 $0.7$ 倍MIDI指定力度，其他则为 $0.9$ 倍。|
|`Ptc`|音符的音高|这是决定音调的参数。以 $P$ 表示此参数， $n$ 表示其在MIDI中的编号， $x$ 表示一定的音调偏移，则计算公式为： $P = 2^\frac{n-60-x}{12}$。之所以存在音调偏移是因为在《我的世界》中，不同的[乐器存在不同的音域](https://zh.minecraft.wiki/wiki/%E9%9F%B3%E7%AC%A6%E7%9B%92#%E4%B9%90%E5%99%A8)，我们通过音调偏移来进行调整。|

### 播放器内容

1.	计分板播放器

	计分板播放器是一种传统的《我的世界》音乐播放方式。通过对于计分板加分来实现播放不同的音符。一个很简单的原理，就是**用不同的计分板分值对应不同的音符**，再通过加分，来达到那个分值，即播放出来。

	在**音·创**中，用来达到这种效果的指令是这样的：

	```mcfunction
	execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
	```



	后四个参数决定了这个音的性质，而前两个参数仅仅是为了决定音播放的时间。

2.	延迟播放器

	延迟播放器是通过《我的世界》游戏中，指令方块的设置项“延迟刻数”来达到定位音符的效果。**将所有的音符依照其播放时距离乐曲开始时的时间（毫秒），放在一个序列内，再计算音符两两之间对应的时间差值，转换为《我的世界》内对应的游戏刻数之后填入指令方块的设置中。**

	在**音·创**中，由于此方式播放的音乐不需要用计分板，所以播放指令是这样的：

	```mcfunction
	execute Tg ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
	```


	其中后四个参数决定了这个音的性质。
	
	由于这样的延迟数据是依赖于指令方块的设置项，所以使用这种播放器所转换出的结果仅可以存储在包含方块NBT信息及方块实体NBT信息的结构文件中，或者直接输出至世界。

3. 中继器播放器

	中继器播放器是一种传统的《我的世界》红石音乐播放方式，利用游戏内“红石组件”之“红石中继器”以达到定位音符之用。**但是，中继器的延迟为1红石刻**


##	文件格式

1.	附加包格式（`.mcpack`）

	使用附加包格式导出音乐，若采用计分板 播放器，则音乐会以指令函数文件（`.mcfunction`）存储于附加包内。而若为延迟或中继器播放器，则音乐回以结构文件（`.mcstructure`）存储。在所生成的附加包中，函数文件的存储结构应为：

	- `functions\`
		- `index.mcfunction`
		- `stop.mcfunction`
		- `mscply\`
			- `progressShow.mcfunction`
			- `track1.mcfunction`
			- `track2.mcfunction`
			- ...
			- `trackN.mcfunction`
	- `structures\`
		- `XXX_main.mcstructure`
		- `XXX_start.mcstructure`
		- `XXX_reset.mcstructure`
		- `XXX_pgb.mcstructure`

	如图，其中，`index.mcfunction`文件、`stop.mcfunction`文件和`mscply`文件夹存在于函数目录的根下；在`mscply`目录中，包含音乐导出的众多音轨播放文件（`trackX.mcfunction`）。同时，若使用计分板播放器生成此包时启用生成进度条，则会包含`progressShow.mcfunction`文件。若选择延迟或中继器播放器，则会生成`structures`目录以及相关`.mcstructure`文件，其中`mian`表示音乐播放用的主要结构；`start`是用于初始化播放的部分，仅包含一个指令方块即起始块；`reset`和`pgb`仅在启用生成进度条时出现，前者用于重置临时计分板，后者用于显示进度条。
	
	`index.mcfunction`用于开始播放：
	
	1.	若为计分板播放器，则其中包含打开各个音轨对应函数的指令，以及加分指令，这里的加分，是将**播放计分板的值大于等于 $1$ 的所有玩家**的播放计分板分数增加 $1$。同时，若生成此包时选择了自动重置计分板的选项，则会包含一条重置计分板的指令。
	
	2.	若为延迟或中继器播放器，则其中的指令仅包含用以正确加载结构的`structure`指令。

	`stop.mcfunction`用于终止播放：
	
	1.	若为计分板播放器，则其中包含将**全体玩家的播放计分板**重置的指令。
	
	2.	若为延迟或中继器播放器，则其中包含**停用命令方块**和**启用命令方块**的指令。~~然鹅实际上对于播放而言是一点用也没有~~

	>	你知道吗？音·创的最早期版本“《我的世界》函数音乐生成器”正是用函数来播放，不过这个版本采取的读入数据的形式大有不同。

2. 生成结构的方式

    无论是音·创生成的是何种结构，`MCSTRUCTURE`还是`BDX`，都会依照此处的格式来生成。此处我们想说明的结构的格式不是结构文件存储的格式，而是结构导出之后方块摆放的方式。结构文件存储的格式这一点，在各个《我的世界》开发的相关网站上都可能会有说明。

	考虑到进行《我的世界》游戏开发时，为了节约常加载区域，很多游戏会将指令区设立为一种层叠式的结构。这种结构会限制每一层的指令系统的高度，但是虽然长宽也是有限的，却仍然比其纵轴延伸得更加自由。

	所以，结构的生成形状依照给定的高度和内含指令的数量决定。其 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整。用数学公式的方式表达，则是：
	
	$$ MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor $$

	其中，$MaxZ$ 即生成结构的$Z$轴最大延伸长度，$NoC$ 表示链结构中所含指令方块的个数，$MaxH$ 表示给定的生成结构的最大高度。

	我们的结构生成器在生成指令链时，将首先以相对坐标系 $(0, 0, 0)$ （即相对原点）开始，自下向上堆叠高度轴（即 $Y$ 轴）的长，当高度轴达到了限制的高度时，便将 $Z$ 轴向正方向堆叠 $1$ 个方块，并开始自上向下重新堆叠，直至高度轴坐标达到相对为 $0$。若当所生成结构的 $Z$ 轴长达到了其最大延伸长度，则此结构生成器将反转 $Z$ 轴的堆叠方向，直至 $Z$ 轴坐标相对为 $0$。如此往复，直至指令链堆叠完成。
	

# 进度条自定义

因为我们提供了可以自动转换进度条的功能，因此在这里给出进度条自定义参数的详细解释。

一个进度条，明显地，有**固定部分**和**可变部分**来构成。而可变部分又包括了文字和图形两种（当然，《我的世界》里头的进度条，可变的图形也就是那个“条”了）。这一点你需要了解，因为后文中包含了很多这方面的概念需要你了解。

进度条的自定义功能使用一个字符串来定义自己的样式，其中包含众多**标识符**来表示可变部分。

标识符如下（注意大小写）：

| 标识符   | 指定的可变量     |
|---------|----------------|
| `%%N`   | 乐曲名(即传入的文件名)|
| `%%s`   | 当前计分板值     |
| `%^s`   | 计分板最大值     |
| `%%t`   | 当前播放时间     |
| `%^t`   | 曲目总时长       |
| `%%%`   | 当前进度比率     |
| `_`     | 用以表示进度条占位|

表示进度条占位的 `_` 是用来标识你的进度条的。也就是可变部分的唯一的图形部分。

**样式定义字符串（基础样式）**的样例如下，这也是默认进度条的基础样式：

```▶ %%N [ %%s/%^s %%% __________ %%t|%^t]```

这是单独一行的进度条，当然你也可以制作多行的，如果是一行的，输出时所使用的指令便是 `title`，而如果是多行的话，输出就会用 `titleraw` 作为进度条字幕。

哦对了，上面的只不过是样式定义，同时还需要定义的是可变图形的部分，也就是进度条上那个真正的“条”。

对于这个我们就采用了固定参数的方法，对于一个进度条，无非就是“已经播放过的”和“没播放过的”两种形态，例如，我们默认的进度“条”（**可变样式**）的定义是这样的：

**可变样式甲（已播放样式）**：`'§e=§r'`

**可变样式乙（未播放样式）**：`'§7=§r'`

综合起来，把这些参数传给函数需要一个参数整合，使用位于 `Musicreater/subclass.py` 下的 `ProgressBarStyle` 类进行定义：

我们的默认定义参数如下：

```python
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
    r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]",
    r"§e=§r",
    r"§7=§r",
)
```

*为了避免生成错误，请尽量避免使用标识符作为定义样式字符串的其他部分*

