+ +
+ + +## Instructions📕 + +> 0. Install Python 3.6+ +> +> While installing, be sure to check "add Python 3.X to path", otherwise it needs to be set manually +> +> After the installation, remember to enter in CMD: "python" to try +> +> whether the installation is successful. +> +> Python installation tutorial can be found on the Internet easily. +> +> 1. Install (download this program) +> Git, you can use the following commands: +> +> `git clone -b pkgver https://gitee.com/EillesWan/Musicreater.git` +> +> If Git is not installed, you can download the zip package from the website. Then decompress it +> and enter the directory. +> +> 2. Run (enter directory) +> +> Open CMD in the directory, enter the directory, and execute the following commands: +> +> `pip install mido` +> +> `pip install brotli` +> +> `pip install openpyxl` +> +> 3. Start using! +> +> Open CMD in the directory, enter the directory, and execute the following commands: +> +> (Choose what you need) +> +> `python example_convert_bdx.py` +> +> `python example_convert_mcpack.py` + +### Instructions for **Customize Progress Bar** + +We have supported the function of making progress bar in *Minecraft*'s music player. And also the method of customize them. So the following instructions are about the parameters of the Progress Bar Customizition. + +A Progress Bar, of course, is composed of **changeless** parts and **changable** parts. And the changable parts include texts or *images*(these images are made up of texts, or we can say, character paintings 😁). That is, for *Minecraft*, a changable image in a progress bar is just the "bar" part(which is like a stripe). + +We use a string to describe the style of progress bar you need, and it includes many **identifier**s to replace the changable parts. + +There are the identifiers: + +| Identifier | Changable Part | +|--------------|------------------------------------------------------| +| `%%N` | Music name(file name which is imported into program) | +| `%%s` | Value of scoreboard of now | +| `%^s` | Max value of scoreboard | +| `%%t` | Current playback time | +| `%^t` | Total music time | +| `%%%` | Current playback progress | +| `_` | To be replaced by the *Bar* part of the progress bar | + +The `_` is a placeholder to identifying the *bar* part, yeah, just the changable image. + +This is an example of **style description string**, and this is also the default style of *Musicreater*'s progress bar. + +`▶ %%N [ %%s/%^s %%% __________ %%t|%^t]` + +This is a progress bar with only one line, but it is possible if you want to give a multiline parameter into the style description string. + +But the string above is only for style identification, but we also need to identifying the changable image's image(just what the bar's look). + +A "bar", simply, included 2 parts: *Have Been Played* & *Not Been Played*. So we use a tuple to pass the parameter. It's under a simple format: `(str: played, str: not)`. For example, the default parameter is below: + +`('§e=§r', '§7=§r')` + +So it's time to combine what I said in one parameter now! + +This is a default definder parameter: + +`('▶ %%N [ %%s/%^s %%% __________ %%t|%^t]',('§e=§r', '§7=§r'))` + +*Tip: To avoid errors, please not to use the identifiers as the other part of your style.* diff --git a/docs/使用教程.md b/docs/使用教程.md new file mode 100644 index 0000000..c5c3c69 --- /dev/null +++ b/docs/使用教程.md @@ -0,0 +1,117 @@ ++ +
+ + +## 使用教程📕 + +> 0. 安装python3.6+ +> +> 在安装时,一定要勾选Add Python 3.X to PATH,不然就要手动设置!! +> +> 同时,装完之后记得在cmd中输入:python 试试是否安装成功, +> python的安装可以去网上随便找一下。 +> 成功安装之后,在cmd中输入python会显示: +> +> 之类的东西。 +> 1. 下载本程序 +> +> git的话,可以使用以下命令: +> +> `git clone -b pkgver https://gitee.com/EillesWan/Musicreater.git` +> +> 没有安装git的话,可以下载zip包,解压后进入目录即可。 +> +> 2. 运行(进入目录) +> 在目录下打开cmd,进入到目录下,执行以下命令: +> +> +> 回车一下,然后: +> +> 输入下面的三个指令即可! +> +> `pip install mido` +> +> `pip install brotli` +> +> `pip install openpyxl` +> +> 3. 开始使用! +> 在目录下打开cmd(步骤与上面的图片一致,只是执行的代码换了),进入到目录下,执行以下命令:(选择你需要的) +> +> `python example_convert_bdx.py` +> +> `python example_convert_mcpack.py` +> +> 4. 错误补充说明 +> 如果你遇到了以下这种情况 +> +> 那么,请按照这篇文章指引做: +> https://blog.csdn.net/qq_41179280/article/details/123804948 +> +> 感谢Mono帮我们发现这个问题 +> +> 5. 使用补充说明 +> +> midi路径:含有mid文件路径、文件名、后缀的完整绝对路径 +> +> 输出路径:输出文件夹的路径,就写一个英文.(句号)可以表示生成到当前目录下 +> (意思就是支持相对路径) +> +> 是否重置计分板:1或0(歌曲放完是否重置,推荐1) +> +> 是否启用进度条:1或0(看个人需要) +> +> 计分板名称:游戏内的计分板名称 +> +> 音量:0-1之间的小数(含0,1)正常来说推荐1 +> +> 变速:float数据,一般写1 +> +> 没有报错且在输出路径下找到mcpack或bdx即为生成成功: +> + + +### 对于 进度条自定义 功能的说明 + +因为我们提供了可以自动转换进度条的功能,因此在这里给出进度条自定义参数的详细解释。 + +一个进度条,明显地,有**固定部分**和**可变部分**来构成。而可变部分又包括了文字和图形两种(当然,《我的世界》里头的进度条,可变的图形也就是那个“条”了)。这一点你需要了解,因为后文中包含了很多这方面的概念需要你了解。 + +进度条的自定义功能使用一个字符串来定义自己的样式,其中包含众多**标识符**来表示可变部分。 + +标识符如下(注意大小写): + +| 标识符 | 指定的可变量 | +|---------|----------------| +| `%%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'))` + +*对了!为了避免生成错误,请尽量避免使用标识符作为定义样式字符串的其他部分* diff --git a/msctPkgver/__init__.py b/msctPkgver/__init__.py index 4bc583d..a2bef6c 100644 --- a/msctPkgver/__init__.py +++ b/msctPkgver/__init__.py @@ -38,5 +38,5 @@ Note! Except for this source file, all the files in this repository and this pro from .main import * -print('此转换功能由音·创开发者开发,版权归参与开发的人员共同所有。') +print('此Midi转换功能由音·创开发者开发,版权归参与开发的人员共同所有。') print('Copyright © 2022 all the developers of Musicreater') diff --git a/msctPkgver/main.py b/msctPkgver/main.py index 3c5fe8f..21098d0 100644 --- a/msctPkgver/main.py +++ b/msctPkgver/main.py @@ -463,7 +463,7 @@ class midiConvert: self, scoreboardname: str = "mscplay", volume: float = 1.0, speed: float = 1.0 ) -> list: """ - 使用金羿的转换思路,将midi转换为我的世界命令列表 + 使用金羿的转换思路,将midi转换为我的世界命令列表,使用线性方法调整音量 :param scoreboardname: 我的世界的计分板名称 :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频 :param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed @@ -483,6 +483,7 @@ class midiConvert: ticks = 0 instrumentID = 0 singleTrack = [] + noteOn = [] for msg in track: ticks += msg.time @@ -494,6 +495,33 @@ class midiConvert: if msg.type == "program_change": # print("TT") instrumentID = msg.program + if msg.type == 'note_on' and msg.velocity != 0: + noteOn.append([msg, msg.note, ticks]) + elif type_[1] is True: + if msg.type == 'note_on' and msg.velocity == 0: + for u in noteOn: + index = 0 + if u[1] == msg.note: + lastMessage = u[0] + lastTick = u[2] + break + index += 1 + trackS.append( + NoteMessage( + msg.channel, + msg.note, + lastMessage.velocity, + lastTick, + ticks - lastTick, + mid, + ) + ) + # print(noteOn) + # print(index) + try: + noteOn.pop(index) + except IndexError: + noteOn.pop(index - 1) if msg.type == "note_on" and msg.velocity != 0: nowscore = round( (ticks * tempo) diff --git a/noteTmain.py b/noteTmain.py new file mode 100644 index 0000000..7978304 --- /dev/null +++ b/noteTmain.py @@ -0,0 +1,144 @@ +import mido + + +def delete_extra_zero(n: float) -> int or float: + """ + 删除多余的0 + ———————————————— + 版权声明:本文为CSDN博主「XerCis」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 + 原文链接:https://blog.csdn.net/lly1122334/article/details/108770141 + 删除小数点后多余的0 + :param n: input + :return: output + """ + n = '{:g}'.format(n) + n = float(n) if '.' in n else int(n) # 含小数点转float否则int + return n + + +def bpm_by_MetaMessage_Set_tempo(tmp: int) -> int or float: + """ + midi文件tempo事件bpm算法。 + A function that's used to compute the bpm of a midiFile, + which algorithm is made up of midiFile's tempo meta message. + :param tmp:输入mid的metaMessage中速度tempo值 + input the tempo value which is in the tempo meta message. + :return:bpm + + This algorithm is made by ©bgArray. + 算法版权归©诸葛亮与八卦阵所有。 + """ + second = tmp / 1000000 + bpm = delete_extra_zero(60 / second) + # debug.dp(bpm) + return bpm + + +class NoteMessage: + def __init__(self, channel, pitch, velocity, startT, lastT, midi, now_bpm, change_bpm=None): + self.channel = channel + self.note = pitch + self.velocity = velocity + self.startTime = startT + self.lastTime = lastT + self.tempo = now_bpm # 这里要程序实现获取bpm可以参考我的程序 + + def mt2gt(mt, tpb_a, bpm_a): + return mt / tpb_a / bpm_a * 60 + self.startTrueTime = mt2gt(self.startTime, midi.ticks_per_beat, self.tempo) # / 20 + # delete_extra_zero(round_up()) + if change_bpm is not None: + self.lastTrueTime = mt2gt(self.lastTime, midi.ticks_per_beat, change_bpm) # / 20 + else: + self.lastTrueTime = mt2gt(self.lastTime, midi.ticks_per_beat, self.tempo) # / 20 + # delete_extra_zero(round_up()) + print((self.startTime * self.tempo) / (midi.ticks_per_beat * 50000)) + + def __str__(self): + return "noteMessage channel=" + str(self.channel) + " note=" + str(self.note) + " velocity=" + \ + str(self.velocity) + " startTime=" + str(self.startTime) + " lastTime=" + str(self.lastTime) + \ + " startTrueTime=" + str(self.startTrueTime) + " lastTrueTime=" + str(self.lastTrueTime) + + +def load(mid: mido.MidiFile): + + type_ = [False, False, False] # note_off / note_on+0 / mixed + + is_tempo = False + + # 预检 + for i, track in enumerate(mid.tracks): + for msg in track: + # print(msg) + if msg.is_meta is not True: + if msg.type == 'note_on' and msg.velocity == 0: + type_[1] = True + elif msg.type == "note_off": + type_[0] = True + if msg.is_meta is True and msg.type == "set_tempo": + is_tempo = True + + if is_tempo is not True: + raise Exception("这个mid没有可供计算时间的tempo事件") + + if type_[0] is True and type_[1] is True: + type_[2] = True + type_[1] = False + type_[0] = False + print(type_) + + bpm = 0 + recent_change_bpm = 0 + is_change_bpm = False + # 实检 + for i, track in enumerate(mid.tracks): + noteOn = [] + trackS = [] + ticks = 0 + for msg in track: + print(msg) + ticks += msg.time + print(ticks) + if msg.is_meta is True and msg.type == "set_tempo": + recent_change_bpm = bpm + bpm = bpm_by_MetaMessage_Set_tempo(msg.tempo) + is_change_bpm = True + # print((ticks * 92) / (mid.ticks_per_beat * 50000)) + # MC_tick = delete_extra_zero(round_up( + # (ticks * 92) / (mid.ticks_per_beat * 50000) + # )) + # print(MC_tick) + # print(ticks / mid.ticks_per_beat / 92 * 60) + if msg.type == 'note_on' and msg.velocity != 0: + noteOn.append([msg, msg.note, ticks]) + if type_[1] is True: + if msg.type == 'note_on' and msg.velocity == 0: + for u in noteOn: + index = 0 + if u[1] == msg.note: + lastMessage = u[0] + lastTick = u[2] + break + index += 1 + print(lastTick) + if is_change_bpm and recent_change_bpm != 0: + trackS.append(NoteMessage(msg.channel, msg.note, lastMessage.velocity, lastTick, ticks - lastTick, + mid, recent_change_bpm, bpm)) + is_change_bpm = False + else: + trackS.append( + NoteMessage(msg.channel, msg.note, lastMessage.velocity, lastTick, ticks - lastTick, + mid, bpm)) + # print(noteOn) + # print(index) + try: + noteOn.pop(index) + except IndexError: + noteOn.pop(index - 1) + print(trackS) + for j in trackS: + print(j) + + +if __name__ == '__main__': + load(mido.MidiFile("test.mid"))