63 Commits

Author SHA1 Message Date
Eilles
bf173a9f81 没有变化 2026-01-08 03:18:20 +08:00
Eilles
6bea46977f 下次要建一个dev分支到仓库里啊…… 2025-11-30 02:51:59 +08:00
Eilles
3b8532af61 2025-11-28 05:21:58 +08:00
Eilles
9f35e9e16a 啊? 2025-11-27 04:18:16 +08:00
Eilles
65358b12d0 气笑了 2025-11-27 04:17:26 +08:00
Eilles
046c2af27c Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-11-27 04:03:41 +08:00
Eilles
fa6cfb4873 神笔编码 2025-11-27 04:03:39 +08:00
EillesWan
3d6d0e17a2 去人工智能那边看看,我直接抄吧 2025-11-20 16:05:30 +08:00
EillesWan
f1bea1d57e Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-10-22 11:50:12 +08:00
EillesWan
63b59442a4 重构么?不重构吧…… 2025-10-22 11:50:04 +08:00
Eilles
7f74b29569 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-09-27 16:56:09 +08:00
Eilles
885dc085ea 我改了啥我都不记得了,但是同步一下代码吧 2025-09-27 16:56:06 +08:00
EillesWan
24742a140c 也是绝了,版本改了之后版本标识没更新,对,我说的是MSQ和FSQ 2025-09-26 09:45:19 +08:00
EillesWan
6f94f477f2 暑假回学校,同步所有更改内容 2025-08-30 19:48:41 +08:00
EillesWan
c056eb1fcb 小问题修复,回复 https://gitee.com/ElapsingDreams/MusicPreview/issues/ICUEFX 的问题 2025-08-24 00:11:01 +08:00
EillesWan
b796a363d8 更丰富的音符附加信息支持 2025-08-20 18:57:21 +08:00
EillesWan
774f78359c Midi歌词支持,文档格式更新 2025-08-19 04:37:11 +08:00
EillesWan
8df63df26c 更新一处细节,避免声相偏移无法听见 2025-07-08 06:00:32 +08:00
EillesWan
9764019f08 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-07-06 02:59:14 +08:00
EillesWan
250857c78f 同步本机更改? 2025-07-06 02:59:10 +08:00
EillesWan
7ab6b77cff Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-06-19 02:30:45 +08:00
EillesWan
321f80540c 完整的全景声相支持,修复了之前音量大小确定错误的问题 2025-06-19 02:30:24 +08:00
EillesWan
50373b05d0 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-05-26 17:36:24 +08:00
EillesWan
b65483b32d 合并双机器工作之变化性更改 2025-05-26 17:33:39 +08:00
EillesWan
45e9adbbd1 支持神羽资源包 2025-05-25 04:20:55 +08:00
EillesWan
7a1ca86132 新增部分可指定的默认值,增加默认乐器替换表 2025-04-15 21:14:38 +08:00
EillesWan
64048a5e31 修复插件兼容性问题,Sorry 2025-04-15 20:21:40 +08:00
EillesWan
3739138059 切换为UV进行包管理 2025-04-15 20:01:43 +08:00
EillesWan
048b631bd6 强化文档内容 2025-04-11 16:58:11 +08:00
EillesWan
23bf69619b 2.3.0,基于FSQ的完整流式音符信息传输支持。 2025-04-11 16:31:54 +08:00
EillesWan
b4e16353ec 更新上传文件 2025-04-08 18:14:50 +08:00
EillesWan
c14489f3a7 MSQ 流式适配与校验增强,新增 NBS 音色表 2025-04-08 17:49:49 +08:00
EillesWan
889f8f9641 Java版!MSQ流式解析初适配! 2025-04-02 02:52:41 +08:00
EillesWan
2df8d6a270 我服了你个老六 2024-12-29 21:00:43 +08:00
EillesWan
78eabf2df6 接口小兼容 2024-12-29 20:48:37 +08:00
EillesWan
b3ddfc7ade 文档改动 2024-12-26 02:02:13 +08:00
EillesWan
bd031ef547 Bug小修 2024-10-22 00:33:54 +08:00
EillesWan
e4304dc3d1 部分API小幅度新增内容 2024-10-22 00:19:39 +08:00
EillesWan
794ee6d080 2024-08-19 02:33:00 +08:00
EillesWan
b67554c7c9 增加一个英文致谢 2024-08-19 02:31:49 +08:00
EillesWan
6f391567ba 部分API小幅新增,修复新版本指令的计分板播放器附加包无法播放的问题 2024-08-19 02:28:28 +08:00
EillesWan
932b9a34e0 修改协议 2024-07-18 16:21:10 +08:00
EillesWan
f6b884e38f 高精度时间支持,修复TEMPO设置错误的问题 2024-06-11 00:54:53 +08:00
EillesWan
bf1b7b99d8 高精度时间支持,修复tempo设置错误的问题 2024-06-11 00:54:50 +08:00
EillesWan
fe7c11636b 解决一些小问题 2024-05-02 02:00:04 +08:00
EillesWan
382b0e1601 websocket播放支持 2024-05-01 01:16:05 +08:00
EillesWan
c17ff0e53a 直接让MusicSave的函数放在主类里吧,更好用一些 2024-03-10 18:52:18 +08:00
EillesWan
f1ab2373b5 完成2.0.0正式版构建,现已支持音乐保存…… 2024-03-10 18:47:10 +08:00
Eilles Wan
77351d767b !4 update Musicreater/constants.INSTRUMENT_OFFSET_POS_TABLE
Merge pull request !4 from ElapsingDreams/N/A
非常好的偏移表
2024-03-10 10:37:19 +00:00
ElapsingDreams
69624df711 update Musicreater/constants.INSTRUMENT_OFFSET_POS_TABLE
依据wiki更改 不同乐器的音调偏离对照表 ,其数值为偏离标准音C3的数值

Signed-off-by: ElapsingDreams <9063072+ElapsingDreams@user.noreply.gitee.com>
2024-03-10 10:26:28 +00:00
EillesWan
d4925e4d75 进一步优化结构,提高插件兼容 2024-03-10 02:22:12 +08:00
EillesWan
95c0ff1b47 2.0.0全新版本,更好的代码组织形式 2024-03-04 00:04:05 +08:00
EillesWan
3831c41b9a 我觉得能用了 2024-02-24 21:31:39 +08:00
EillesWan
a4565b8529 如改 2024-02-08 18:29:29 +08:00
EillesWan
041c64ff65 优化example结构,支持mcstructure的计分板播放 2024-02-03 16:54:43 +08:00
EillesWan
b0bdb7b445 修复了乐音乐器的execute前缀缺失的问题 2024-02-02 20:50:35 +08:00
EillesWan
99a7564648 修复了打击乐器转换时,注释部分format代码报错的问题 2024-02-02 18:12:29 +08:00
EillesWan
dbb3f4c83f 修复了一个小兼容性bug 2024-01-23 18:36:25 +08:00
EillesWan
edd40c078f 加个copy方法 2024-01-23 18:17:15 +08:00
EillesWan
d3b637a0c8 修复类型定义的重复问题-2 2024-01-23 18:14:41 +08:00
EillesWan
d7e3c62deb 修复类型定义的重复问题 2024-01-23 17:01:42 +08:00
EillesWan
7b319268fe 改个版本号 2024-01-14 22:08:27 +08:00
EillesWan
41883f7798 可以用了(?) 2024-01-14 22:05:55 +08:00
67 changed files with 9572 additions and 2231 deletions

12
.gitignore vendored
View File

@@ -1,14 +1,17 @@
# sth. can't open
/msctPkgver/secrets/*.py
/msctPkgver/secrets/*.c
/fool/
# mystuff
/*.zip
/.vscode
/*.mid
/*.midi
/*.mcpack
/*.bdx
/*.msq
/*.fsq
/*.json
/*.mcstructure
.mscbackup
@@ -18,6 +21,12 @@
/utils
test.py
RES.txt
/MSCT_Packer.py
/Packer/*.MPK
/Packer/checksum.txt
/bgArrayLib
/fcwslib
test_lyric-mido.py
# Byte-compiled / optimized
__pycache__/
@@ -43,6 +52,7 @@ share/python-wheels/
.installed.cfg
*.egg
MANIFEST
.pdm-build/
# PyInstaller
# Usually these files are written by a python script from a template

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.10

View File

@@ -1,219 +1,123 @@
**注意以下条款或版权声明应当且必须是高于此项目中任何其他声明的**
# 汉钰律许可协议,第一版
1. ·创的全部开发者享有其完整版权其开发者可以在任一时刻终止以后音·创源代码开放若经由其开发者授予特殊权利则授权对象可以将源代码进行特定的被特殊授权的操作
2. ·创或其代码允许在 Apache2.0 协议的条款与说明下进行非商业使用
3. 除部分代码特殊声明外·创允许对其或其代码进行商业化使用但是需要经过音·创主要开发者诸葛亮与八卦阵金羿的一致授权同时授权对象在商业化授权的使用过程中必须依照 Apache2.0 协议的条款与说明
4. 若存在对于音·创包含的部分代码的特殊开源声明则此部分代码依照其特定的开源方式授权但若此部分代码经由此部分代码的主要开发者一致特殊授权后商用则授权对象在商用时依照此部分的开发者所准许的方式或条款进行商用
5. Apache2.0 协议的英文原文副本可见下文
**总第一版 第二次修订 · 二〇二四年七月七日编 二〇二五年四月二十六日修订**
> The English Translation of the TERMS AND CONDITIONS above is listed below
>
> This translated version is for reference only and has no legal effect.
>
> The version with legal effect is the Chinese version above.
## 一、重要须知
**Note, The TERMS AND CONDITIONS below should and must be above all others in this project**
1. 为保护采用本协议的作品在开源过程中其著作权人所应有的权益根据**中华人民共和国著作权法和相关法律法规**制定本协议
1. *Musicreater* is fully copyrighted by all its developers, the developers have the right to make *Musicreater* close sourced at any time. Operations are permitted under specific terms instructed by its developer(s).
2. Non-commercial use of *Musicreater* and(or) its source code is permitted under Apache License 2.0.
3. Commercial use of *Musicreater* is permitted under Apache License 2.0 with the unanimous permission of the steering developers of *Musicreater* (*bgArray*诸葛亮与八卦阵 and *Eilles*金羿).
4. *Musicreater* is open sourced under priority given:
1. License granted by the core developer(s) of a section after negotiation.
2. Explicitly stated license.
3. Apache 2.0 License.
5. A copy of the original Apache Lisence 2.0 can be found below.
2. 本协议履行过程中请注意本协议中**免除或限制**民事主体**责任或权利**的条款法律适用和争议解决条款尤其是加有特殊标记的条款这些条款应在中国法律所允许的范围内最大程度地适用
3. 若本协议所涉及的自然人**未满 18 周岁**该自然人应在监护人的陪同下阅读本协议及有关本协议的条款内容并在取得其监护人同意后开始或继续应用本协议所授权的行为
```text
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
4. 由于互联网服务互联网内容的特殊性若本协议以电子协议形式分发并签订其依然有效您一旦开始对本协议所授权之作品进行本协议所授权的行为即视为您已经阅读理解并同意并已经接受本协议的全部条款
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
5. 本协议的订立履行解释及争议的解决均**适用中华人民共和国法律并排除其他一切冲突法的适用**_本协议订立于许可证最初的颁发者的地址若颁发者为自然人则订立于该自然人户籍所在地若为法人或非法人组织则订立于其注册地_本协议的订立各方应友好协商解决于协议所规定之行为的履行相关的争议如协商不成任何一方均可向合同签订地有管辖权的人民法院提起诉讼
1. Definitions.
6. 本协议的原本仅为现代汉语书写于简体中文若存在其他语言的翻译或其他同等语言但非简体中文文本的版本应当无法律效力
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
## 二、术语定义
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
1. **许可证****协议**后文称本协议是指根据本文档中所列举的全部术语定义条款限制等文本是本合同的简称称谓本合同全称是 **汉钰律许可协议第一版**
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
2. **协议颁发者**后文称颁发者是将条款或协议应用于其拥有著作财产权的作品的民事主体或由其指定从而拥有颁发者身份的民事主体
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
3. ****形式是指对包括但不限于 软件硬件文档配置项 等种类的作品进行修改编辑的首选形式若不存在首选形式则初次编辑该作品所需的形式即为源形式
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
4. **目标**形式是指对源形式进行机械转换翻译打印制造加工等同类型活动后形成的结果形式包括但不限于源代码编译后的目标软件生成的文件转换出的媒体制造出的机械打印出的实体文本加工后的零件
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
5. **采用本协议的作品**后文称此作品是指经颁发者授权而使用本协议进行授权的任何作品该作品应在自然人可见处明确附加一个自然人可读的版权通知可以参考文末附录中提供的示例若在一个可分割的作品中部分地采用本协议进行授权则该部分应当视为一个独立的采用本协议的作品该作品应当在自然人可见处明确附加一个自然人可读的范围限定和版权通知同样可以参考文末附录中提供的示例
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
6. **贡献**是指对作品进行的意在提交给此作品颁发者以让著作权人包含在其作品中的任何修订或补充该修订或补充同样属于一种作品依据此定义**提交**一词表示经由此作品颁发者所指定的形式将其所进行的修改发送给此作品颁发者该形式应当包括在此作品颁发者指定的平台内发送易于编辑的修改信息在此作品颁发者指定的电子邮箱中发送易于编辑的修改信息在此作品颁发者指定的源码控制系统或发布跟踪系统上提交的易于编辑的修改信息但由著作权人以明显标注或指定为非贡献的活动除外颁发者自己对作品进行的修改同样视作对作品的贡献
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
7. **贡献者**是指此作品颁发者接受的贡献的提交者或包含在作品的贡献清单中的民事主体贡献者在提交贡献并经此作品颁发者通过且该贡献已经被应用于此作品中后该贡献者应当视为此作品的著作权人之一但不应视为此作品非其贡献的部分的著作权人一个作品的颁发者同样属于其贡献者**请注意**针对贡献者提交的贡献该贡献者应被视为该贡献的协议颁发者但不应视作本作品的颁发者
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
8. **用户****使用者**是指行使本协议所授权之行为的民事主体据此贡献者亦属于用户
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
9. **商业性使用****商用**是指任何以谋取利益为目的的使用包括但不限于以贩卖出租的形式对作品进行使用但若将该获取利益之活动明确指示为捐赠且在获利者在进行本协议所授权的活动时不以捐赠数额为标准而区别之则此种的获取利益的捐赠行为不属于商业性使用
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
## 三、权利授予
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
1. 任何由颁发者所进行的特殊声明特别注意等此类内容应当在法律效力上高于本协议的条款或声明这些声明若与本协议冲突本协议的该冲突部分无效本协议与这些声明共同构成颁发者与用户之间的合同
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
2. 此作品的贡献者享有其贡献的完整著作权
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
3. 此作品的贡献者将自己的贡献的全部著作财产权免费公开不可撤销无限期非专有地授予此作品的全部著作权人并准许其在全世界范围内使用上述权利若无明确的标识贡献者允许此作品的颁发者对其贡献进行免费公开不可撤销无限期非专有世界范围内的商业性使用
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
4. 此作品的著作权人及贡献者授予用户**免费公开不可撤销非专有非商用**地以任意形式**复制发行展览表演放映广播信息网络传播摄制改编翻译汇编二次授权**的权利准许其在此作品颁发者所指定的区域与时间内行使上述权利若此作品颁发者未特别指定的则视作在全世界范围内无限期地授权若此作品颁发者特别指定在特定情况下可以商用则应当按照其所指定的条件进行商业性使用商用的过程中应当明确标识此作品的著作权人
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
5. 一旦此作品有任意由非贡献形式而产生的更改更改的部分将不视为此作品的一部分除非该部分不可离开此作品单独存在若该部分必须依赖此作品而不可与此作品分离从而单独存在则更改后的作品不视作此作品在这种情况下除非此更改后的作品已获得此作品颁发者的特殊许可或更改者即为此作品颁发者本人否则对该作品进行的任何活动都应当遵守本协议
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
6. 经贡献而产生的对此作品的更改属于此作品的一部分在此情况下更改后的作品依旧视作此作品
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
7. 依据本款的第 4 若用户在本协议的授权下将此作品授予他人进行任何形式的活动二次授权二次分发则应确保其使用的协议或授权内容与本协议的条款不冲突当存在与本协议条款的冲突时则该冲突内容无效被授权的第三方应依照本协议的条款进行活动除非该用户获得了此作品颁发者的特殊许可或该用户即为此作品颁发者本人
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
8. 依据本款的第 5 若由非贡献形式而产生更改的部分是可分割而不需依赖此作品即可单独存在的若该部分明确注明不使用本协议进行授权或明确声明了其他授权条款则该部分不视作采用本协议但未更改的部分仍应视作原此作品的一部分需要采用本协议进行授权除非此更改后的作品已获得此作品颁发者的特殊许可或更改者即为此作品颁发者本人
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
9. 若此作品或所提交的贡献包含其著作权人的专利则该专利所有人即此作品的著作权人应准许此作品全体著作权人**免费公开不可撤销非专有无版权费的专利许可**以便贡献者对作品进行本协议所授权进行的活动
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
10. 上述专利许可的授予仅适用于在所提交的贡献中可由专利所有者授予的且在对此作品进行本协议所授权的活动中必须使用的专利
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
11. 如果用户对任何民事主体因其在进行本协议所授权进行的活动中侵犯该用户的专利而提起诉讼那么根据本协议授予该用户的所有关于此作品的任何其他专利许可将在提起上述诉讼之日起终止
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
12. 如果本作品作为用户的其他作品的不可分割的一部分进行任何民事活动本协议依旧对本作品即该用户的其他作品的一部分生效若本作品完全融入该用户的其他作品之中而不可独立存在则该用户需要保证其作品存在与本协议冲突的条款除非该作品已获得此作品颁发者的特殊许可或该用户即为此作品颁发者本人
END OF TERMS AND CONDITIONS
## 四、使用条件
在对此作品进行本协议所授权的民事活动中应当同时满足以下条款
Copyright 2023 TriM-Organization 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & all the developers of Musicreater
1. 用户必须为此作品的任何其他接收者提供本协议的副本在不得已无法提供副本的情况下也应明确指示其他接收者可查阅本协议的位置
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
2. 用户必须在修改后的作品中附带明显的通知声明用户已更改文件并注明更改位置
http://www.apache.org/licenses/LICENSE-2.0
3. 若用户二次分发此作品可以选择向此作品的接收者提供无偿或有偿的担保维修支持服务或其他责任义务但是该用户只可以其自己的名义提供上述内容不得以任何其他贡献者的名义且该用户必须明确表明任何此类责任或义务是由其个人独立提供且其同意并应当承担赔偿此作品的全体贡献者因其个人承担上述责任义务而产生的任何赔偿责任
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
4. 用户不得删除或更改此作品中包含的任何许可声明包括版权声明专利声明免责声明或赔偿责任限制除非该更改是对已知事实错误的修补或其已获得此作品颁发者的特殊许可或更改者即为此作品颁发者本人
5. 若此作品将权益的声明通知作为一部分那么由用户分发的任何版本的作品中须至少在下列三处之一包含该声明通知的自然人可读副本
- 该作品的权益声明通知中
- 在源形式的文件中当且仅当该作品开放源代码
- 在惯例中作为第三方通知出现之处当且仅当该作品会产生画面且该画面可被自然人详细观察
该通知的内容仅供信息提供不应对许可证进行任何文字上的修改用户可在其分发的作品中在不构成修改本协议的前提下在作品自身的声明通知或属性描述后或作为附录添加
6. 依据本款第3条若用户二次分发此作品时选择向作品的接收者提供收费的担保服务则必须明确告知该接收者本协议全部内容与此作品原出处并确保其知悉上述内容但若用户在二次分发此作品时不选择提供任何服务则该用户不允许向作品的接收者收取任何费用除非该用户获得了此作品颁发者的特殊许可或该用户即为此作品颁发者本人
## 五、提交贡献
除非贡献者明确声明在本作品中由该贡献者向颁发者的提供的提交必须符合本协议的条款并与本协议的条款不存在冲突除非此贡献中与本协议冲突的附加条款已获得颁发者的特殊许可或贡献者即为此作品颁发者本人
## 六、商标相关
本协议并未授予用户将颁发者的商标专属标记或特定产品名称用于合理的或惯例性的描述或此类声明之外其他任何位置的权利
## 七、免责声明
1. 若非因法律要求或经过了特殊准许此作品在根据本协议原样提供的基础上**不予提供任何形式的担保任何明示任何暗示或类似承诺**此类包括但不限于担保此作品毫无缺陷担保此作品适于贩卖担保此作品适于特定目的担保使用此作品绝不侵权用户将自行承担因此作品的质量或性能问题而产生的全部风险若此作品在任何方面欠妥将由用户而非任何贡献者而非任何颁发者承担所有必要的服务维修或除错的任何成本本免责声明是本许可的重要组成部分当且仅当遵守本免责声明时本协议的其他条款中对本作品的使用授权方可生效
2. 无论是因何种原因如果不是在法律规定的特殊情况确为贡献者的故意或重大过失下或者经过了特殊准许即使贡献者事先已知发生损害的可能在使用本作品时用户产生的任何直接间接特殊偶然或必然造成的损失包括但不限于商誉损失工作延误计算机系统故障等**均不由任一贡献者承担**
**以上是本许可协议的全部条款**
---
附录
**如何在自己的作品中应用 汉钰律许可协议**
若要在自己源形式的作品应用本协议请在其中附加下面的通知模板并将六角括号中的字段替换成自身的实际信息来替换不包括括号本身这些文本必须以对应文件格式适当的注释句法包含在其中可以是实体的纸质文档也可以是网络公告或者计算机文件或者脱离该源之外另起一个新的文件使之指向要应用本协议的那个作品同时也建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的页面上作为版权通知的整体这样更加容易的区分出第三方内容
若需要在自己以目标形式存在的作品中应用本协议同样需要附加下面的通知模板并更改六角括号中的字样但是这些文本可以是位于作品的标签上位于作品的用户可见且能被自然人详细观察的画面之中或者按照惯例中许可协议应该出现的位置同时这些文本的所处位置应当能够明确指示到本协议应用的那个作品另外建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的位置上作为版权通知的整体这样更加容易的区分出第三方内容
**通知模板**
```
版权所有 © 年份 著作权人
或者版权所有 (C) 年份 著作权人
该作品根据 汉钰律许可协议第一版本协议授权
任何人皆可从以下地址获得本协议副本本协议副本所在地址
若非因法律要求或经过了特殊准许此作品在根据本协议原样提供的基础上不予提供任何形式的担保任何明示任何暗示或类似承诺也就是说用户将自行承担因此作品的质量或性能问题而产生的全部风险
详细的准许和限制条款请见原协议文本
```

View File

@@ -1,15 +1,20 @@
# -*- coding: utf-8 -*-
"""一个简单的我的世界音频转换库
音·创 (Musicreater)
是一款免费开源的针对《我的世界》的midi音乐转换库
是一款免费开源的《我的世界》数字音频支持库。
Musicreater(音·创)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
A free open source library used for dealing with **Minecraft** digital musics.
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
The Licensor of Musicreater("this project") is Eilles, bgArray.
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
任何人皆可从以下地址获得本协议副本https://gitee.com/EillesWan/YulvLicenses。
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
详细的准许和限制条款请见原协议文本。
"""
# 睿乐组织 开发交流群 861684859
@@ -17,21 +22,123 @@ Terms & Conditions: License.md in the root directory
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__version__ = "1.6.2"
__vername__ = "新的转换算法测试,更好的声音适配"
__version__ = "2.4.2.3"
__vername__ = "音符附加信息升级"
__author__ = (
("金羿", "Eilles Wan"),
("金羿", "Eilles"),
("诸葛亮与八卦阵", "bgArray"),
("鱼旧梦", "ElapsingDreams"),
("偷吃不是Touch", "Touch"),
("鸣凤鸽子", "MingFengPigeon"),
)
__all__ = [
# 主要类
"MusicSequence",
"MidiConvert",
# 附加类
"SingleNote",
"SingleCommand",
# "SingleNote",
"MineNote",
"MineCommand",
"SingleNoteBox",
"ProgressBarStyle",
# "TimeStamp", 未来功能
# 字典键
"MIDI_PROGRAM",
"MIDI_VOLUME",
"MIDI_PAN",
# 默认值
"MIDI_DEFAULT_PROGRAM_VALUE",
"MIDI_DEFAULT_VOLUME_VALUE",
"DEFAULT_PROGRESSBAR_STYLE",
# Midi 自己的对照表
"MIDI_PITCH_NAME_TABLE",
"MIDI_PITCHED_NOTE_NAME_GROUP",
"MIDI_PITCHED_NOTE_NAME_TABLE",
"MIDI_PERCUSSION_NOTE_NAME_TABLE",
# Minecraft 自己的对照表
"MC_PERCUSSION_INSTRUMENT_LIST",
"MC_PITCHED_INSTRUMENT_LIST",
"MC_INSTRUMENT_BLOCKS_TABLE",
"MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE",
"MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE",
# Midi 与 游戏 的对照表
"MM_INSTRUMENT_RANGE_TABLE",
"MM_INSTRUMENT_DEVIATION_TABLE",
"MM_CLASSIC_PITCHED_INSTRUMENT_TABLE",
"MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE",
"MM_TOUCH_PITCHED_INSTRUMENT_TABLE",
"MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE",
"MM_DISLINK_PITCHED_INSTRUMENT_TABLE",
"MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE",
"MM_NBS_PITCHED_INSTRUMENT_TABLE",
"MM_NBS_PERCUSSION_INSTRUMENT_TABLE",
# 操作性函数
"velocity_2_distance_natural",
"velocity_2_distance_straight",
"panning_2_rotation_linear",
"panning_2_rotation_trigonometric",
# 工具函数
"load_decode_musicsequence_metainfo",
"load_decode_msq_flush_release",
"load_decode_fsq_flush_release",
"guess_deviation",
"mctick2timestr",
"midi_inst_to_mc_sound",
]
from .main import *
from .main import MusicSequence, MidiConvert
from .subclass import (
MineNote,
MineCommand,
SingleNoteBox,
ProgressBarStyle,
mctick2timestr,
DEFAULT_PROGRESSBAR_STYLE,
)
from .utils import (
# 兼容性函数
load_decode_musicsequence_metainfo,
load_decode_msq_flush_release,
load_decode_fsq_flush_release,
# 工具函数
guess_deviation,
midi_inst_to_mc_sound,
# 处理用函数
velocity_2_distance_natural,
velocity_2_distance_straight,
panning_2_rotation_linear,
panning_2_rotation_trigonometric,
)
from .constants import (
# 字典键
MIDI_PROGRAM,
MIDI_PAN,
MIDI_VOLUME,
# 默认值
MIDI_DEFAULT_PROGRAM_VALUE,
MIDI_DEFAULT_VOLUME_VALUE,
# MIDI 表
MIDI_PITCH_NAME_TABLE,
MIDI_PITCHED_NOTE_NAME_GROUP,
MIDI_PITCHED_NOTE_NAME_TABLE,
MIDI_PERCUSSION_NOTE_NAME_TABLE,
# 我的世界 表
MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE,
MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
MC_INSTRUMENT_BLOCKS_TABLE,
MC_PERCUSSION_INSTRUMENT_LIST,
MC_PITCHED_INSTRUMENT_LIST,
# MIDI 到 我的世界 表
MM_INSTRUMENT_RANGE_TABLE,
MM_INSTRUMENT_DEVIATION_TABLE,
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
MM_DISLINK_PITCHED_INSTRUMENT_TABLE,
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE,
MM_NBS_PITCHED_INSTRUMENT_TABLE,
MM_NBS_PERCUSSION_INSTRUMENT_TABLE,
)

File diff suppressed because it is too large Load Diff

421
Musicreater/data.py Normal file
View File

@@ -0,0 +1,421 @@
# -*- coding: utf-8 -*-
"""
存储音·创新数据存储类
"""
# WARNING 本文件中使用之功能尚未启用
"""
版权所有 © 2025 金羿
Copyright © 2025 Eilles
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf
from dataclasses import dataclass
from typing import Optional, Any, List, Tuple, Union, Dict, Sequence, Callable
import bisect
from .types import FittingFunctionType
from .constants import MC_PITCHED_INSTRUMENT_LIST
class ArgumentCurve:
base_line: float = 0
"""基线/默认值"""
default_curve: Callable[[float], float]
"""默认曲线"""
defined_curves: Dict[float, "ArgumentCurve"] = {}
"""调整后的曲线集合"""
left_border: float = 0
"""定义域左边界"""
right_border: float = inf
"""定义域右边界"""
def __init__(self, baseline: float = 0, default_function: Callable[[float], float] = lambda x: 0, function_set: Dict = {}) -> None:
pass
def __call__(self, *args: Any, **kwds: Any) -> Any:
pass
class SoundAtmos:
sound_distance: float
"""声源距离 方块"""
sound_azimuth: Tuple[float, float]
"""声源方位 角度"""
def __init__(
self,
distance: Optional[float] = None,
azimuth: Optional[Tuple[float, float]] = None,
) -> None:
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
"""声源方位"""
# 如果指定为零,那么为零,但如果不指定或者指定为负数,则为 0.01 的距离
self.sound_distance = (
(16 if distance > 16 else (distance if distance >= 0 else 0.01))
if distance is not None
else 0.01
)
"""声源距离"""
@classmethod
def from_displacement(
cls,
displacement: Optional[Tuple[float, float, float]] = None,
) -> "SoundAtmos":
if displacement is None:
# displacement = (0, 0, 0)
return cls()
else:
r = sqrt(displacement[0] ** 2 + displacement[1] ** 2 + displacement[2] ** 2)
if r == 0:
return cls(distance=0, azimuth=(0, 0))
else:
beta_h = round(degrees(asin(displacement[1] / r)), 8)
if displacement[2] == 0:
alpha_v = -90 if displacement[0] > 0 else 90
else:
alpha_v = round(
degrees(atan(-displacement[0] / displacement[2])), 8
)
return cls(distance=r, azimuth=(alpha_v, beta_h))
@property
def position_displacement(self) -> Tuple[float, float, float]:
"""声像位移"""
dk1 = self.sound_distance * round(cos(radians(self.sound_azimuth[1])), 8)
return (
-dk1 * round(sin(radians(self.sound_azimuth[0])), 8),
self.sound_distance * round(sin(radians(self.sound_azimuth[1])), 8),
dk1 * round(cos(radians(self.sound_azimuth[0])), 8),
)
@dataclass(init=False)
class SingleNote:
"""存储单个音符的类"""
note_pitch: int
"""midi音高"""
velocity: int
"""力度"""
start_tick: int
"""开始之时 命令刻"""
duration: int
"""音符持续时间 命令刻"""
high_precision_time: int
"""高精度开始时间偏量 1/1250 秒"""
extra_info: Dict[str, Any]
"""你觉得放什么好?"""
def __init__(
self,
midi_pitch: Optional[int],
midi_velocity: int,
start_time: int,
last_time: int,
mass_precision_time: int = 0,
extra_information: Dict[str, Any] = {},
):
"""
用于存储单个音符的类
Parameters
------------
midi_pitch: int
midi音高
midi_velocity: int
midi响度(力度)
start_time: int
开始之时(命令刻)
注:此处的时间是用从乐曲开始到当前的刻数
last_time: int
音符延续时间(命令刻)
mass_precision_time: int
高精度的开始时间偏移量(1/1250秒)
is_percussion: bool
是否作为打击乐器
distance: float
发声源距离玩家的距离(半径 `r`
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
azimuth: tuple[float, float]
声源方位
此参数为tuple包含两个元素分别表示
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
extra_information: Dict[str, Any]
附加信息,尽量存储为字典
Returns
---------
MineNote 类
"""
self.note_pitch: int = 66 if midi_pitch is None else midi_pitch
"""midi音高"""
self.velocity: int = midi_velocity
"""响度(力度)"""
self.start_tick: int = start_time
"""开始之时 命令刻"""
self.duration: int = last_time
"""音符持续时间 命令刻"""
self.high_precision_time: int = mass_precision_time
"""高精度开始时间偏量 0.4 毫秒"""
self.extra_info = extra_information if extra_information else {}
@classmethod
def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
"""自字节码析出 SingleNote 类"""
duration_ = (
group_1 := int.from_bytes(code_buffer[:6], "big")
) & 0b11111111111111111
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
note_velocity_ = (group_1 := group_1 >> 17) & 0b1111111
note_pitch_ = (group_1 := group_1 >> 7) & 0b1111111
try:
return cls(
midi_pitch=note_pitch_,
midi_velocity=note_velocity_,
start_time=start_tick_,
last_time=duration_,
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
)
except:
print(
"[Error] 单音符解析错误,字节码`{}`{}启用高精度时间偏移\n".format(
code_buffer, "" if is_high_time_precision else ""
)
)
raise
def encode(self, is_high_time_precision: bool = True) -> bytes:
"""
将数据打包为字节码
Parameters
------------
is_high_time_precision: bool
是否启用高精度,默认为**是**
Returns
---------
bytes
打包好的字节码
"""
# SingleNote 的字节码
# note_pitch 7 位 支持到 127
# velocity 长度 7 位 支持到 127
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# 共 48 位 合 6 字节
# high_time_precision可选长度 8 位 支持到 255 合 1 字节 支持 1/1250 秒]
return (
(
(
((((self.note_pitch << 7) + self.velocity) << 17) + self.start_tick)
<< 17
)
+ self.duration
).to_bytes(6, "big")
# + self.track_no.to_bytes(1, "big")
+ (
self.high_precision_time.to_bytes(1, "big")
if is_high_time_precision
else b""
)
)
def set_info(self, key: Union[str, Sequence[str]], value: Any):
"""设置附加信息"""
if isinstance(key, str):
self.extra_info[key] = value
elif (
isinstance(key, Sequence)
and isinstance(value, Sequence)
and (k := len(key)) == len(value)
):
for i in range(k):
self.extra_info[key[i]] = value[i]
else:
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
def get_info(self, key: str, default: Any = None) -> Any:
"""获取附加信息"""
return self.extra_info.get(key, default)
def stringize(self, include_extra_data: bool = False) -> str:
return "TrackedNote(Pitch = {}, Velocity = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
self.note_pitch,
self.velocity,
self.start_tick,
self.duration,
self.high_precision_time,
) + (
", ExtraData = {})".format(self.extra_info) if include_extra_data else ")"
)
# def __list__(self) -> List[int]:
# 我不认为这个类应当被作为列表使用
def __tuple__(
self,
) -> Tuple[int, int, int, int, int]:
return (
self.note_pitch,
self.velocity,
self.start_tick,
self.duration,
self.high_precision_time,
)
def __dict__(self):
return {
"Pitch": self.note_pitch,
"Velocity": self.velocity,
"StartTick": self.start_tick,
"Duration": self.duration,
"TimeOffset": self.high_precision_time,
"ExtraData": self.extra_info,
}
def __eq__(self, other) -> bool:
"""比较两个音符是否具有相同的属性,不计附加信息"""
if not isinstance(other, self.__class__):
return False
return self.__tuple__() == other.__tuple__()
def __lt__(self, other) -> bool:
"""比较自己是否在开始时间上早于另一个音符"""
if self.start_tick == other.start_tick:
return self.high_precision_time < other.high_precision_time
else:
return self.start_tick < other.start_tick
def __gt__(self, other) -> bool:
"""比较自己是否在开始时间上晚于另一个音符"""
if self.start_tick == other.start_tick:
return self.high_precision_time > other.high_precision_time
else:
return self.start_tick > other.start_tick
class SingleTrack(list):
"""存储单个轨道的类"""
track_name: str
"""轨道之名称"""
track_instrument: str
"""乐器ID"""
track_volume: float
"""该音轨的音量"""
is_high_time_precision: bool
"""该音轨是否使用高精度时间"""
is_percussive: bool
"""该音轨是否标记为打击乐器轨道"""
sound_position: SoundAtmos
"""声像方位"""
argument_curves: Dict[str, FittingFunctionType]
"""参数曲线"""
extra_info: Dict[str, Any]
"""你觉得放什么好?"""
def __init__(
self,
name: str = "未命名音轨",
instrument: str = "",
volume: float = 0,
precise_time: bool = True,
percussion: bool = False,
sound_direction: SoundAtmos = SoundAtmos(),
extra_information: Dict[str, Any] = {},
*args: SingleNote,
):
self.track_name = name
"""音轨名称"""
self.track_instrument = instrument
"""乐器ID"""
self.track_volume = volume
"""音量"""
self.is_high_time_precision = precise_time
"""是否使用高精度时间"""
self.is_percussive = percussion
"""是否为打击乐器"""
self.sound_position = sound_direction
"""声像方位"""
self.extra_info = extra_information if extra_information else {}
super().__init__(*args)
@property
def note_amount(self) -> int:
"""音符数"""
return len(self)
def set_info(self, key: Union[str, Sequence[str]], value: Any):
"""设置附加信息"""
if isinstance(key, str):
self.extra_info[key] = value
elif (
isinstance(key, Sequence)
and isinstance(value, Sequence)
and (k := len(key)) == len(value)
):
for i in range(k):
self.extra_info[key[i]] = value[i]
else:
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
def get_info(self, key: str, default: Any = None) -> Any:
"""获取附加信息"""
return self.extra_info.get(key, default)
class SingleMusic:
pass

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
"""
存放一报错类型
存放一报错类型
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -18,11 +18,11 @@ Terms & Conditions: License.md in the root directory
class MSCTBaseException(Exception):
"""音·创库版本的所有错误均继承于此"""
"""音·创 的所有错误均继承于此"""
def __init__(self, *args):
"""音·创库版本的所有错误均继承于此"""
super().__init__(*args)
"""音·创 的所有错误均继承于此"""
super().__init__("音·创", *args)
def meow(
self,
@@ -35,11 +35,11 @@ class MSCTBaseException(Exception):
class MidiFormatException(MSCTBaseException):
"""音·创库版本的所有MIDI格式错误均继承于此"""
"""音·创 的所有MIDI格式错误均继承于此"""
def __init__(self, *args):
"""音·创库版本的所有MIDI格式错误均继承于此"""
super().__init__("MIDI格式错误", *args)
"""音·创 的所有MIDI格式错误均继承于此"""
super().__init__("MIDI 格式错误", *args)
class MidiDestroyedError(MSCTBaseException):
@@ -47,18 +47,19 @@ class MidiDestroyedError(MSCTBaseException):
def __init__(self, *args):
"""Midi文件损坏"""
super().__init__("MIDI文件损坏无法读取MIDI文件", *args)
super().__init__("MIDI文件损坏无法读取 MIDI 文件", *args)
class MidiUnboundError(MSCTBaseException):
"""未定义Midi对象"""
# class MidiUnboundError(MSCTBaseException):
# """未定义Midi对象(无用)"""
def __init__(self, *args):
"""未绑定Midi对象"""
super().__init__("未定义MidiFile对象你甚至没有对象就想要生孩子", *args)
# def __init__(self, *args):
# """未绑定Midi对象"""
# super().__init__("未定义MidiFile对象你甚至没有对象就想要生孩子", *args)
# 此错误在本版本内已经不再使用
class CommandFormatError(RuntimeError):
class CommandFormatError(MSCTBaseException, RuntimeError):
"""指令格式与目标格式不匹配而引起的错误"""
def __init__(self, *args):
@@ -75,6 +76,10 @@ class CommandFormatError(RuntimeError):
# 这TM是什么错误
# 我什么时候写的这玩意?
# 我哪知道这说的是啥?
#
# 我知道这是什么了 —— 金羿 2025 0401
# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
class NotDefineTempoError(MidiFormatException):
@@ -82,7 +87,7 @@ class NotDefineTempoError(MidiFormatException):
def __init__(self, *args):
"""没有Tempo设定导致时间无法计算的错误"""
super().__init__("在曲目开始时没有声明Tempo未指定拍长", *args)
super().__init__("在曲目开始时没有声明 Tempo未指定拍长", *args)
class ChannelOverFlowError(MidiFormatException):
@@ -109,9 +114,49 @@ class NoteOnOffMismatchError(MidiFormatException):
super().__init__("音符不匹配", *args)
class ZeroSpeedError(ZeroDivisionError):
class LyricMismatchError(MSCTBaseException):
"""歌词匹配解析错误"""
def __init__(self, *args):
"""有可能产生了错误的歌词解析"""
super().__init__("歌词解析错误", *args)
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
"""以0作为播放速度的错误"""
def __init__(self, *args):
"""以0作为播放速度的错误"""
super().__init__("播放速度为0", *args)
super().__init__("播放速度为", *args)
class IllegalMinimumVolumeError(MSCTBaseException, ValueError):
"""最小播放音量有误的错误"""
def __init__(self, *args):
"""最小播放音量错误"""
super().__init__("最小播放音量超出范围", *args)
class MusicSequenceDecodeError(MSCTBaseException):
"""音乐序列解码错误"""
def __init__(self, *args):
"""音乐序列无法正确解码的错误"""
super().__init__("解码音符序列文件时出现问题", *args)
class MusicSequenceTypeError(MSCTBaseException):
"""音乐序列类型错误"""
def __init__(self, *args):
"""无法识别音符序列字节码的类型"""
super().__init__("错误的音符序列字节类型", *args)
class MusicSequenceVerificationFailed(MusicSequenceDecodeError):
"""音乐序列校验失败"""
def __init__(self, *args):
"""音符序列文件与其校验值不一致"""
super().__init__("音符序列文件校验失败", *args)

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
# 音·创 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
# 版权所有 金羿("Eilles") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
@@ -22,24 +22,24 @@
Musicreater (音·创)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 ../License.md
Terms & Conditions: ../License.md
"""
# ============================
import mido
class NoteMessage:
def __init__(self, channel, pitch, velocity, startT, lastT, midi, now_bpm, change_bpm=None):
def __init__(
self, channel, pitch, velocity, startT, lastT, midi, now_bpm, change_bpm=None
):
self.channel = channel
self.note = pitch
self.velocity = velocity
@@ -49,19 +49,39 @@ class NoteMessage:
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
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
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
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)
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):
@@ -75,7 +95,7 @@ def load(mid: mido.MidiFile):
for msg in track:
# print(msg)
if msg.is_meta is not True:
if msg.type == 'note_on' and msg.velocity == 0:
if msg.type == "note_on" and msg.velocity == 0:
type_[1] = True
elif msg.type == "note_off":
type_[0] = True
@@ -105,13 +125,13 @@ def load(mid: mido.MidiFile):
print(ticks)
if msg.is_meta is True and msg.type == "set_tempo":
recent_change_bpm = bpm
bpm = 60000000 / msg.tempo
bpm = 60000000 / msg.tempo
is_change_bpm = True
if msg.type == 'note_on' and msg.velocity != 0:
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:
if msg.type == "note_on" and msg.velocity == 0:
for u in noteOn:
index = 0
if u[1] == msg.note:
@@ -121,13 +141,31 @@ def load(mid: mido.MidiFile):
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))
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))
NoteMessage(
msg.channel,
msg.note,
lastMessage.velocity,
lastTick,
ticks - lastTick,
mid,
bpm,
)
)
# print(noteOn)
# print(index)
try:
@@ -139,20 +177,20 @@ def load(mid: mido.MidiFile):
print(j)
if __name__ == '__main__':
if __name__ == "__main__":
load(mido.MidiFile("test.mid"))
# ============================
from typing import Literal
from ..constants import x,y,z
from ..constants import x, y, z
# 不要用 没写完
def delay_to_note_blocks(
baseblock: str = "stone",
position_forward: Literal['x','y','z'] = z,
baseblock: str = "stone",
position_forward: Literal["x", "y", "z"] = z,
):
"""传入音符,生成以音符盒存储的红石音乐
:param:
@@ -161,7 +199,7 @@ def delay_to_note_blocks(
:return 是否生成成功
"""
from TrimMCStruct import Structure, Block
from TrimMCStruct import Block, Structure
struct = Structure(
(_sideLength, max_height, _sideLength), # 声明结构大小
@@ -169,8 +207,7 @@ def delay_to_note_blocks(
log = print
startpos = [0,0,0]
startpos = [0, 0, 0]
# 1拍 x 2.5 rt
for i in notes:
@@ -180,7 +217,10 @@ def delay_to_note_blocks(
[startpos[0], startpos[1] + 1, startpos[2]],
form_note_block_in_NBT_struct(height2note[i[0]], instrument),
)
struct.set_block(startpos, Block("universal_minecraft", instuments[i[0]][1]),)
struct.set_block(
startpos,
Block("universal_minecraft", instuments[i[0]][1]),
)
error = False
except ValueError:
log("无法放置音符:" + str(i) + "" + str(startpos))
@@ -222,4 +262,4 @@ def delay_to_note_blocks(
struct.set_block(Block("universal_minecraft", baseblock), startpos)
startpos[0] += posadder[0]
startpos[1] += posadder[1]
startpos[2] += posadder[2]
startpos[2] += posadder[2]

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
"""
存放非音·创本体的附加内容(插件?)
存放非音·创本体的附加功能件
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -15,8 +15,59 @@ Terms & Conditions: License.md in the root directory
__all__ = [
"ConvertConfig",
# 通用
# "ConvertConfig",
"bottem_side_length_of_smallest_square_bottom_box",
# 打包
"compress_zipfile",
"behavior_mcpack_manifest",
# MCSTRUCTURE 函数
"antiaxis",
"forward_IER",
"command_statevalue",
"form_note_block_in_NBT_struct",
"form_repeater_in_NBT_struct",
"form_command_block_in_NBT_struct",
"commands_to_structure",
"commands_to_redstone_delay_structure",
# MCSTRUCTURE 常量
"AXIS_PARTICULAR_VALUE",
"COMPABILITY_VERSION_117",
"COMPABILITY_VERSION_119",
"COMPABILITY_VERSION_121",
# BDX 函数
"bdx_move",
"form_command_block_in_BDX_bytes",
"commands_to_BDX_bytes",
# BDX 常量
"BDX_MOVE_KEY",
]
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"))
__author__ = (("金羿", "Eilles"), ("诸葛亮与八卦阵", "bgArray"))
from .main import *
# from .main import ConvertConfig
from .archive import compress_zipfile, behavior_mcpack_manifest
from .bdx import (
BDX_MOVE_KEY,
bdx_move,
form_command_block_in_BDX_bytes,
commands_to_BDX_bytes,
)
from .common import bottem_side_length_of_smallest_square_bottom_box
from .mcstructure import (
antiaxis,
forward_IER,
AXIS_PARTICULAR_VALUE,
COMPABILITY_VERSION_119,
COMPABILITY_VERSION_117,
COMPABILITY_VERSION_121,
command_statevalue,
form_note_block_in_NBT_struct,
form_repeater_in_NBT_struct,
form_command_block_in_NBT_struct,
commands_to_structure,
commands_to_redstone_delay_structure,
)

View File

@@ -2,8 +2,8 @@
"""
用以生成附加包的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -18,11 +18,13 @@ __all__ = [
"to_addon_pack_in_delay",
"to_addon_pack_in_score",
"to_addon_pack_in_repeater",
"to_addon_pack_in_repeater_divided_by_instrument",
]
__author__ = (("金羿", "Eilles Wan"),)
__author__ = (("金羿", "Eilles"),)
from .main import (
to_addon_pack_in_delay,
to_addon_pack_in_repeater,
to_addon_pack_in_score,
to_addon_pack_in_repeater_divided_by_instrument,
)

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -14,16 +14,15 @@ Terms & Conditions: License.md in the root directory
import json
import os
import shutil
from typing import Tuple
from TrimMCStruct import Structure
from typing import Literal, Optional, Tuple
from ...main import MidiConvert
from ...subclass import ProgressBarStyle
from ..archive import behavior_mcpack_manifest, compress_zipfile
from ..main import ConvertConfig
from ..mcstructure import (
COMPABILITY_VERSION_117,
COMPABILITY_VERSION_119,
Structure,
commands_to_redstone_delay_structure,
commands_to_structure,
form_command_block_in_NBT_struct,
@@ -32,7 +31,8 @@ from ..mcstructure import (
def to_addon_pack_in_score(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
) -> Tuple[int, int]:
@@ -43,8 +43,10 @@ def to_addon_pack_in_score(
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
@@ -56,21 +58,25 @@ def to_addon_pack_in_score(
"""
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_score(
scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier
scoreboard_name=scoreboard_name,
)
# 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目然后创建
if os.path.exists(f"{data_cfg.dist_path}/temp/functions/"):
shutil.rmtree(f"{data_cfg.dist_path}/temp/functions/")
os.makedirs(f"{data_cfg.dist_path}/temp/functions/mscplay")
if os.path.exists(f"{dist_path}/temp/functions/"):
shutil.rmtree(f"{dist_path}/temp/functions/")
os.makedirs(f"{dist_path}/temp/functions/mscplay")
# 写入manifest.json
with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
with open(f"{dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包MCFUNCTION(MCPACK) 计分播放器 - 由 音·创 生成",
pack_name=midi_cvt.midi_music_name + "播放",
pack_description=f"{midi_cvt.music_name} 音乐播放包MCFUNCTION(MCPACK) 计分播放器 - 由 音·创 生成",
pack_name=midi_cvt.music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
format_version=1 if midi_cvt.enable_old_exe_format else 2,
pack_engine_version=(
None if midi_cvt.enable_old_exe_format else [1, 19, 50]
),
),
fp=f,
indent=4,
@@ -78,18 +84,18 @@ def to_addon_pack_in_score(
# 写入stop.mcfunction
with open(
f"{data_cfg.dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
f"{dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write("scoreboard players reset @a {}".format(scoreboard_name))
# 将命令列表写入文件
index_file = open(
f"{data_cfg.dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
f"{dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
for i in range(len(cmdlist)):
index_file.write(f"function mscplay/track{i + 1}\n")
with open(
f"{data_cfg.dist_path}/temp/functions/mscplay/track{i + 1}.mcfunction",
f"{dist_path}/temp/functions/mscplay/track{i + 1}.mcfunction",
"w",
encoding="utf-8",
) as f:
@@ -102,22 +108,24 @@ def to_addon_pack_in_score(
+ scoreboard_name
+ " 1\n",
(
"scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(maxscore + 20)
+ "..}]"
+ f" {scoreboard_name}\n"
)
if auto_reset
else "",
f"function mscplay/progressShow\n" if data_cfg.progressbar_style else "",
(
"scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(maxscore + 20)
+ "..}]"
+ f" {scoreboard_name}\n"
)
if auto_reset
else ""
),
f"function mscplay/progressShow\n" if progressbar_style else "",
)
)
if data_cfg.progressbar_style:
if progressbar_style:
with open(
f"{data_cfg.dist_path}/temp/functions/mscplay/progressShow.mcfunction",
f"{dist_path}/temp/functions/mscplay/progressShow.mcfunction",
"w",
encoding="utf-8",
) as f:
@@ -126,7 +134,7 @@ def to_addon_pack_in_score(
[
single_cmd.cmd
for single_cmd in midi_cvt.form_progress_bar(
maxscore, scoreboard_name, data_cfg.progressbar_style
maxscore, scoreboard_name, progressbar_style
)
]
)
@@ -134,21 +142,22 @@ def to_addon_pack_in_score(
index_file.close()
if os.path.exists(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack"):
os.remove(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack")
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.mcpack"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.mcpack")
compress_zipfile(
f"{data_cfg.dist_path}/temp/",
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[score].mcpack",
)
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
shutil.rmtree(f"{dist_path}/temp/")
return maxlen, maxscore
def to_addon_pack_in_delay(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
player: str = "@a",
max_height: int = 64,
) -> Tuple[int, int]:
@@ -159,8 +168,10 @@ def to_addon_pack_in_delay(
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
player: str
玩家选择器,默认为`@a`
max_height: int
@@ -178,35 +189,38 @@ def to_addon_pack_in_delay(
)
command_list, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
# 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建
if os.path.exists(f"{data_cfg.dist_path}/temp/"):
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
os.makedirs(f"{data_cfg.dist_path}/temp/functions/")
os.makedirs(f"{data_cfg.dist_path}/temp/structures/")
if os.path.exists(f"{dist_path}/temp/"):
shutil.rmtree(f"{dist_path}/temp/")
os.makedirs(f"{dist_path}/temp/functions/")
os.makedirs(f"{dist_path}/temp/structures/")
# 写入manifest.json
with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
with open(f"{dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包MCSTRUCTURE(MCPACK) 延迟播放器 - 由 音·创 生成",
pack_name=midi_cvt.midi_music_name + "播放",
pack_description=f"{midi_cvt.music_name} 音乐播放包MCSTRUCTURE(MCPACK) 延迟播放器 - 由 音·创 生成",
pack_name=midi_cvt.music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
format_version=1 if midi_cvt.enable_old_exe_format else 2,
pack_engine_version=(
None if midi_cvt.enable_old_exe_format else [1, 19, 50]
),
),
fp=f,
indent=4,
ensure_ascii=False,
)
# 写入stop.mcfunction
with open(
f"{data_cfg.dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
f"{dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write(
"gamerule commandblocksenabled false\ngamerule commandblocksenabled true"
@@ -214,7 +228,7 @@ def to_addon_pack_in_delay(
# 将命令列表写入文件
index_file = open(
f"{data_cfg.dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
f"{dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
struct, size, end_pos = commands_to_structure(
@@ -225,9 +239,9 @@ def to_addon_pack_in_delay(
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_main.mcstructure",
f"{midi_cvt.music_name}_main.mcstructure",
)
),
"wb+",
@@ -236,8 +250,8 @@ def to_addon_pack_in_delay(
del struct
if data_cfg.progressbar_style:
scb_name = midi_cvt.midi_music_name[:3] + "Pgb"
if progressbar_style:
scb_name = midi_cvt.music_name[:3] + "Pgb"
index_file.write("scoreboard objectives add {0} dummy {0}\n".format(scb_name))
struct_a = Structure((1, 1, 1), compability_version=compability_ver)
@@ -257,19 +271,19 @@ def to_addon_pack_in_delay(
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_start.mcstructure",
f"{midi_cvt.music_name}_start.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
index_file.write(f"structure load {midi_cvt.midi_music_name}_start ~ ~ ~1\n")
index_file.write(f"structure load {midi_cvt.music_name}_start ~ ~ ~1\n")
pgb_struct, pgbSize, pgbNowPos = commands_to_structure(
midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style),
midi_cvt.form_progress_bar(max_delay, scb_name, progressbar_style),
max_height - 1,
compability_version_=compability_ver,
)
@@ -277,16 +291,16 @@ def to_addon_pack_in_delay(
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_pgb.mcstructure",
f"{midi_cvt.music_name}_pgb.mcstructure",
)
),
"wb+",
) as f:
pgb_struct.dump(f)
index_file.write(f"structure load {midi_cvt.midi_music_name}_pgb ~ ~1 ~1\n")
index_file.write(f"structure load {midi_cvt.music_name}_pgb ~ ~1 ~1\n")
struct_a = Structure(
(1, 1, 1),
@@ -307,9 +321,9 @@ def to_addon_pack_in_delay(
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_reset.mcstructure",
f"{midi_cvt.music_name}_reset.mcstructure",
)
),
"wb+",
@@ -319,34 +333,37 @@ def to_addon_pack_in_delay(
del struct_a, pgb_struct
index_file.write(
f"structure load {midi_cvt.midi_music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
f"structure load {midi_cvt.music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
)
index_file.write(
f"structure load {midi_cvt.midi_music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
f"structure load {midi_cvt.music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
)
else:
index_file.write(f"structure load {midi_cvt.midi_music_name}_main ~ ~ ~1\n")
index_file.write(f"structure load {midi_cvt.music_name}_main ~ ~ ~1\n")
index_file.close()
if os.path.exists(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack"):
os.remove(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack")
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.mcpack"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.mcpack")
compress_zipfile(
f"{data_cfg.dist_path}/temp/",
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[delay].mcpack",
)
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
shutil.rmtree(f"{dist_path}/temp/")
return len(command_list), max_delay
def to_addon_pack_in_repeater(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
player: str = "@a",
axis_side: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
basement_block: str = "concrete",
max_height: int = 65,
) -> Tuple[int, int]:
"""
@@ -356,8 +373,10 @@ def to_addon_pack_in_repeater(
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
player: str
玩家选择器,默认为`@a`
max_height: int
@@ -375,27 +394,29 @@ def to_addon_pack_in_repeater(
)
command_list, max_delay, max_together = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
# 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建
if os.path.exists(f"{data_cfg.dist_path}/temp/"):
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
os.makedirs(f"{data_cfg.dist_path}/temp/functions/")
os.makedirs(f"{data_cfg.dist_path}/temp/structures/")
if os.path.exists(f"{dist_path}/temp/"):
shutil.rmtree(f"{dist_path}/temp/")
os.makedirs(f"{dist_path}/temp/functions/")
os.makedirs(f"{dist_path}/temp/structures/")
# 写入manifest.json
with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
with open(f"{dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包MCSTRUCTURE(MCPACK) 中继器播放器 - 由 音·创 生成",
pack_name=midi_cvt.midi_music_name + "播放",
pack_description=f"{midi_cvt.music_name} 音乐播放包MCSTRUCTURE(MCPACK) 中继器播放器 - 由 音·创 生成",
pack_name=midi_cvt.music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
format_version=1 if midi_cvt.enable_old_exe_format else 2,
pack_engine_version=(
None if midi_cvt.enable_old_exe_format else [1, 19, 50]
),
),
fp=f,
indent=4,
@@ -403,7 +424,7 @@ def to_addon_pack_in_repeater(
# 写入stop.mcfunction
with open(
f"{data_cfg.dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
f"{dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write(
"gamerule commandblocksenabled false\ngamerule commandblocksenabled true"
@@ -411,21 +432,23 @@ def to_addon_pack_in_repeater(
# 将命令列表写入文件
index_file = open(
f"{data_cfg.dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
f"{dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
struct, size, end_pos = commands_to_redstone_delay_structure(
command_list,
max_delay,
max_together,
commands=command_list,
delay_length=max_delay,
max_multicmd_length=max_together,
base_block=basement_block,
axis_=axis_side,
compability_version_=compability_ver,
)
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_main.mcstructure",
f"{midi_cvt.music_name}_main.mcstructure",
)
),
"wb+",
@@ -434,8 +457,8 @@ def to_addon_pack_in_repeater(
del struct
if data_cfg.progressbar_style:
scb_name = midi_cvt.midi_music_name[:3] + "Pgb"
if progressbar_style:
scb_name = midi_cvt.music_name[:3] + "Pgb"
index_file.write("scoreboard objectives add {0} dummy {0}\n".format(scb_name))
struct_a = Structure((1, 1, 1), compability_version=compability_ver)
@@ -455,19 +478,19 @@ def to_addon_pack_in_repeater(
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_start.mcstructure",
f"{midi_cvt.music_name}_start.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
index_file.write(f"structure load {midi_cvt.midi_music_name}_start ~ ~ ~1\n")
index_file.write(f"structure load {midi_cvt.music_name}_start ~ ~ ~1\n")
pgb_struct, pgbSize, pgbNowPos = commands_to_structure(
midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style),
midi_cvt.form_progress_bar(max_delay, scb_name, progressbar_style),
max_height - 1,
compability_version_=compability_ver,
)
@@ -475,16 +498,16 @@ def to_addon_pack_in_repeater(
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_pgb.mcstructure",
f"{midi_cvt.music_name}_pgb.mcstructure",
)
),
"wb+",
) as f:
pgb_struct.dump(f)
index_file.write(f"structure load {midi_cvt.midi_music_name}_pgb ~ ~1 ~1\n")
index_file.write(f"structure load {midi_cvt.music_name}_pgb ~ ~1 ~1\n")
struct_a = Structure(
(1, 1, 1),
@@ -505,9 +528,9 @@ def to_addon_pack_in_repeater(
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_reset.mcstructure",
f"{midi_cvt.music_name}_reset.mcstructure",
)
),
"wb+",
@@ -517,25 +540,145 @@ def to_addon_pack_in_repeater(
del struct_a, pgb_struct
index_file.write(
f"structure load {midi_cvt.midi_music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
f"structure load {midi_cvt.music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
)
index_file.write(
f"structure load {midi_cvt.midi_music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
f"structure load {midi_cvt.music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
)
else:
index_file.write(f"structure load {midi_cvt.midi_music_name}_main ~ ~ ~1\n")
index_file.write(f"structure load {midi_cvt.music_name}_main ~ ~ ~1\n")
index_file.close()
if os.path.exists(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack"):
os.remove(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack")
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.mcpack"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.mcpack")
compress_zipfile(
f"{data_cfg.dist_path}/temp/",
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[repeater].mcpack",
)
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
shutil.rmtree(f"{dist_path}/temp/")
return len(command_list), max_delay
def to_addon_pack_in_repeater_divided_by_instrument(
midi_cvt: MidiConvert,
dist_path: str,
player: str = "@a",
max_height: int = 65,
base_block: str = "concrete",
axis_side: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
) -> Tuple[int, int]:
"""
将midi以中继器播放器形式转换为mcstructure结构文件后打包成附加包并在附加包中生成相应地导入函数
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
# 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建
if os.path.exists(f"{dist_path}/temp/"):
shutil.rmtree(f"{dist_path}/temp/")
os.makedirs(f"{dist_path}/temp/functions/")
os.makedirs(f"{dist_path}/temp/structures/")
# 写入manifest.json
with open(f"{dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.music_name} 音乐播放包MCSTRUCTURE(MCPACK) 中继器播放器(拆分) - 由 音·创 生成",
pack_name=midi_cvt.music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
format_version=1 if midi_cvt.enable_old_exe_format else 2,
pack_engine_version=(
None if midi_cvt.enable_old_exe_format else [1, 19, 50]
),
),
fp=f,
indent=4,
)
# 写入stop.mcfunction
with open(
f"{dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write(
"gamerule commandblocksenabled false\ngamerule commandblocksenabled true"
)
# 将命令列表写入文件
index_file = open(
f"{dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
cmd_dict, max_delay, max_multiple_cmd_count = (
midi_cvt.to_command_list_in_delay_devided_by_instrument(
player_selector=player,
)
)
base_height = 0
for inst, cmd_list in cmd_dict.items():
struct, size, end_pos = commands_to_redstone_delay_structure(
cmd_list,
max_delay,
max_multiple_cmd_count[inst],
base_block,
axis_=axis_side,
compability_version_=compability_ver,
)
bkn = "{}_{}".format(midi_cvt.music_name, inst.replace(".", "-"))
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
"{}_main.mcstructure".format(bkn),
)
),
"wb+",
) as f:
struct.dump(f)
index_file.write("structure load {}_main ~ ~{} ~3\n".format(bkn, base_height))
base_height += 2 + size[1]
index_file.close()
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.mcpack"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.mcpack")
compress_zipfile(
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[repeater-div].mcpack",
)
shutil.rmtree(f"{dist_path}/temp/")
return midi_cvt.total_note_count, max_delay

View File

@@ -5,8 +5,8 @@
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -25,12 +25,28 @@ from typing import List, Literal, Union
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
"""使用compression指定的算法打包目录为zip文件\n
默认算法为DEFLATED(8),可用算法如下:\n
STORED = 0\n
DEFLATED = 8\n
BZIP2 = 12\n
LZMA = 14\n
"""
使用指定的压缩算法将目录打包为zip文件
Parameters
------------
sourceDir: str
要压缩的源目录路径
outFilename: str
输出的zip文件路径
compression: int, 可选
压缩算法默认为8 (DEFLATED)
可用算法:
STORED = 0
DEFLATED = 8 (默认)
BZIP2 = 12
LZMA = 14
exceptFile: list[str], 可选
需要排除在压缩包外的文件名称列表(可选)
Returns
---------
None
"""
zipf = zipfile.ZipFile(outFilename, "w", compression)
@@ -46,10 +62,12 @@ def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
def behavior_mcpack_manifest(
format_version: Union[Literal[1], Literal[2]] = 1,
pack_description: str = "",
pack_version: Union[List[int], Literal[None]] = None,
pack_name: str = "",
pack_uuid: Union[str, Literal[None]] = None,
pack_engine_version: Union[List[int], None] = None,
modules_description: str = "",
modules_version: List[int] = [0, 0, 1],
modules_uuid: Union[str, Literal[None]] = None,
@@ -64,8 +82,8 @@ def behavior_mcpack_manifest(
now_date.month * 100 + now_date.day,
now_date.hour * 100 + now_date.minute,
]
return {
"format_version": 1,
result = {
"format_version": format_version,
"header": {
"description": pack_description,
"version": pack_version,
@@ -81,3 +99,6 @@ def behavior_mcpack_manifest(
}
],
}
if pack_engine_version:
result["header"]["min_engine_version"] = pack_engine_version
return result

View File

@@ -4,8 +4,8 @@
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -18,10 +18,10 @@ Terms & Conditions: License.md in the root directory
from typing import List
from ..constants import x, y, z
from ..subclass import SingleCommand
from ..subclass import MineCommand
from .common import bottem_side_length_of_smallest_square_bottom_box
bdx_key = {
BDX_MOVE_KEY = {
"x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"],
"y": [b"\x11", b"\x10", b"\x1d", b"\x16", b"\x17"],
"z": [b"\x13", b"\x12", b"\x1e", b"\x18", b"\x19"],
@@ -34,21 +34,18 @@ def bdx_move(axis: str, value: int):
if value == 0:
return b""
if abs(value) == 1:
return bdx_key[axis][0 if value == -1 else 1]
return BDX_MOVE_KEY[axis][0 if value == -1 else 1]
pointer = sum(
[
1 if i else 0
for i in (
value != -1,
value < -1 or value > 1,
value < -128 or value > 127,
value < -32768 or value > 32767,
)
value != -1,
value < -1 or value > 1,
value < -128 or value > 127,
value < -32768 or value > 32767,
]
)
return bdx_key[axis][pointer] + value.to_bytes(
return BDX_MOVE_KEY[axis][pointer] + value.to_bytes(
2 ** (pointer - 2), "big", signed=True
)
@@ -63,50 +60,55 @@ def form_command_block_in_BDX_bytes(
customName: str = "",
executeOnFirstTick: bool = False,
trackOutput: bool = True,
):
) -> bytes:
"""
使用指定项目返回指定的指令方块放置指令项
:param command: `str`
使用指定参数生成指定的指令方块放置指令项
Parameters
------------
command: str
指令
:param particularValue:
particularValue: int
方块特殊值,即朝向
:0 下 无条件
:1 上 无条件
:2 z轴负方向 无条件
:3 z轴正方向 无条件
:4 x轴负方向 无条件
:5 x轴正方向 无条件
:6 下 无条件
:7 下 无条件
:8 下 有条件
:9 上 有条件
:10 z轴方向 有条件
:11 z轴正方向 有条件
:12 x轴方向 有条件
:13 x轴正方向 有条件
:14 下 有条件
:14 下 有条件
:0 下 无条件
:1 上 无条件
:2 z轴负方向 无条件
:3 z轴正方向 无条件
:4 x轴负方向 无条件
:5 x轴正方向 无条件
:6 下 无条件
:7 下 无条件
:8 下 有条件
:9 上 有条件
:10 z轴负方向 有条件
:11 z轴方向 有条件
:12 x轴负方向 有条件
:13 x轴方向 有条件
:14 下 有条件
:14 下 有条件
注意此处特殊值中的条件会被下面condition参数覆写
:param impluse: `int 0|1|2`
impluse: int (0|1|2)
方块类型
0脉冲 1循环 2连锁
:param condition: `bool`
0脉冲 1循环 2连锁
condition: bool
是否有条件
:param needRedstone: `bool`
needRedstone: bool
是否需要红石
:param tickDelay: `int`
tickDelay: int
执行延时
:param customName: `str`
customName: str
悬浮字
lastOutput: `str`
上次输出字符串,注意此处需要留空
:param executeOnFirstTick: `bool`
首刻执行(循环指令方块是否激活后立即执行若为False则从激活时起延迟后第一次执行)
:param trackOutput: `bool`
是否输出
lastOutput: str
命令方块的上次输出字符串,注意此处需要留空
executeOnFirstTick: bool
是否启用首刻执行循环指令方块是否激活后立即执行若为False则从激活时起延迟后第一次执行
trackOutput: bool
是否启用命令方块输出
:return:str
Returns
---------
bytes
用以生成 bdx 结构的字节码
"""
block = b"\x24" + particularValue.to_bytes(2, byteorder="big", signed=False)
@@ -126,13 +128,23 @@ def form_command_block_in_BDX_bytes(
def commands_to_BDX_bytes(
commands_list: List[SingleCommand],
commands_list: List[MineCommand],
max_height: int = 64,
):
"""
:param commands: 指令列表(指令, 延迟)
:param max_height: 生成结构最大高度
:return 成功与否,成功返回(True,未经过压缩的源,结构占用大小),失败返回(False,str失败原因)
指令列表转换为用以生成 bdx 结构的字节码
Parameters
------------
commands: list[tuple[str, int]]
指令列表,每个元素为 (指令, 延迟)
max_height: int
生成结构最大高度
Returns
---------
tuple[bool, bytes, int] or tuple[bool, str]
成功与否,成功返回 (True, 未经过压缩的源, 结构占用大小),失败返回 (False, str失败原因)
"""
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
@@ -150,18 +162,20 @@ def commands_to_BDX_bytes(
for command in commands_list:
_bytes += form_command_block_in_BDX_bytes(
command.command_text,
(1 if y_forward else 0)
if (
((now_y != 0) and (not y_forward))
or (y_forward and (now_y != (max_height - 1)))
)
else (
(3 if z_forward else 2)
(
(1 if y_forward else 0)
if (
((now_z != 0) and (not z_forward))
or (z_forward and (now_z != _sideLength - 1))
((now_y != 0) and (not y_forward))
or (y_forward and (now_y != (max_height - 1)))
)
else (
(3 if z_forward else 2)
if (
((now_z != 0) and (not z_forward))
or (z_forward and (now_z != _sideLength - 1))
)
else 5
)
else 5
),
impluse=2,
condition=command.conditional,
@@ -196,13 +210,13 @@ def commands_to_BDX_bytes(
):
now_z -= 1 if z_forward else -1
z_forward = not z_forward
_bytes += bdx_key[x][1]
_bytes += BDX_MOVE_KEY[x][1]
now_x += 1
else:
_bytes += bdx_key[z][int(z_forward)]
_bytes += BDX_MOVE_KEY[z][int(z_forward)]
else:
_bytes += bdx_key[y][int(y_forward)]
_bytes += BDX_MOVE_KEY[y][int(y_forward)]
return (
_bytes,

View File

@@ -2,8 +2,8 @@
"""
用以生成BDX结构文件的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -15,6 +15,6 @@ Terms & Conditions: License.md in the root directory
__all__ = ["to_BDX_file_in_score", "to_BDX_file_in_delay"]
__author__ = (("金羿", "Eilles Wan"),)
__author__ = (("金羿", "Eilles"),)
from .main import to_BDX_file_in_delay, to_BDX_file_in_score

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -13,11 +13,12 @@ Terms & Conditions: License.md in the root directory
import os
from typing import Optional
import brotli
from ...main import MidiConvert
from ...subclass import SingleCommand
from ...subclass import MineCommand, ProgressBarStyle
from ..bdx import (
bdx_move,
commands_to_BDX_bytes,
@@ -26,12 +27,12 @@ from ..bdx import (
y,
z,
)
from ..main import ConvertConfig
def to_BDX_file_in_score(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
author: str = "Eilles",
@@ -44,8 +45,10 @@ def to_BDX_file_in_score(
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
@@ -61,16 +64,14 @@ def to_BDX_file_in_score(
"""
cmdlist, command_count, max_score = midi_cvt.to_command_list_in_score(
scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier
scoreboard_name=scoreboard_name,
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx")
),
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[score].bdx")),
"w+",
) as f:
f.write("BD@")
@@ -83,7 +84,7 @@ def to_BDX_file_in_score(
midi_cvt.music_command_list
+ (
[
SingleCommand(
MineCommand(
command="scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
@@ -99,11 +100,9 @@ def to_BDX_file_in_score(
max_height - 1,
)
if data_cfg.progressbar_style:
if progressbar_style:
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
midi_cvt.form_progress_bar(
max_score, scoreboard_name, data_cfg.progressbar_style
),
midi_cvt.form_progress_bar(max_score, scoreboard_name, progressbar_style),
max_height - 1,
)
_bytes += pgbBytes
@@ -118,9 +117,7 @@ def to_BDX_file_in_score(
_bytes += cmdBytes
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx")
),
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[score].bdx")),
"ab+",
) as f:
f.write(brotli.compress(_bytes + b"XE"))
@@ -130,7 +127,8 @@ def to_BDX_file_in_score(
def to_BDX_file_in_delay(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
player: str = "@a",
author: str = "Eilles",
max_height: int = 64,
@@ -142,8 +140,10 @@ def to_BDX_file_in_delay(
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
player: str
玩家选择器,默认为`@a`
author: str
@@ -157,18 +157,14 @@ def to_BDX_file_in_delay(
"""
cmdlist, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx")
),
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[delay].bdx")),
"w+",
) as f:
f.write("BD@")
@@ -179,8 +175,8 @@ def to_BDX_file_in_delay(
cmdBytes, size, finalPos = commands_to_BDX_bytes(cmdlist, max_height - 1)
if data_cfg.progressbar_style:
scb_name = midi_cvt.midi_music_name[:3] + "Pgb"
if progressbar_style:
scb_name = midi_cvt.music_name[:3] + "Pgb"
_bytes += form_command_block_in_BDX_bytes(
r"scoreboard objectives add {} dummy {}".replace(r"{}", scb_name),
1,
@@ -195,7 +191,7 @@ def to_BDX_file_in_delay(
)
_bytes += bdx_move(y, 1)
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style),
midi_cvt.form_progress_bar(max_delay, scb_name, progressbar_style),
max_height - 1,
)
_bytes += pgbBytes
@@ -216,9 +212,7 @@ def to_BDX_file_in_delay(
_bytes += cmdBytes
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx")
),
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[delay].bdx")),
"ab+",
) as f:
f.write(brotli.compress(_bytes + b"XE"))

View File

@@ -4,8 +4,8 @@
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -19,9 +19,22 @@ Terms & Conditions: License.md in the root directory
import math
def bottem_side_length_of_smallest_square_bottom_box(total: int, maxHeight: int):
"""给定总方块数量和最大高度,返回所构成的图形外切正方形的边长
:param total: 总方块数量
:param maxHeight: 最大高度
:return: 外切正方形边长 int"""
return math.ceil(math.sqrt(math.ceil(total / maxHeight)))
def bottem_side_length_of_smallest_square_bottom_box(
_total_block_count: int, _max_height: int
):
"""
给定结构的总方块数量和规定的最大高度,返回该结构应当构成的图形,在底面的外切正方形边长
Parameters
------------
_total_block_count: int
总方块数量
_max_height: int
规定的结构最大高度
Returns
---------
int
外切正方形的边长
"""
return math.ceil(math.sqrt(math.ceil(_total_block_count / _max_height)))

View File

@@ -4,8 +4,8 @@
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -16,71 +16,77 @@ Terms & Conditions: License.md in the root directory
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from dataclasses import dataclass
from typing import Literal, Tuple, Union
# from dataclasses import dataclass
# from typing import Literal, Tuple, Union
from ..constants import DEFAULT_PROGRESSBAR_STYLE
# from ..subclass import DEFAULT_PROGRESSBAR_STYLE, ProgressBarStyle
@dataclass(init=False)
class ConvertConfig:
"""
转换通用设置存储类
"""
# @dataclass(init=False)
# class ConvertConfig: # 必定要改
# """
# 转换通用设置存储类
# """
volume_ratio: float
"""音量比例"""
# progressbar_style: Union[ProgressBarStyle, None]
# """进度条样式"""
speed_multiplier: float
"""速度倍率"""
# dist_path: str
# """输出目录"""
progressbar_style: Union[Tuple[str, Tuple[str, str]], Literal[None]]
"""进度条样式组"""
# def __init__(
# self,
# output_path: str,
# progressbar: Union[bool, Tuple[str, Tuple[str, str]], ProgressBarStyle] = True,
# ignore_progressbar_param_error: bool = False,
# ):
# """
# 将已经转换好的数据内容指令载入MC可读格式
dist_path: str
"""输出目录"""
# Parameters
# ----------
# output_path: str
# 生成内容的输出目录
# volume: float
# 音量比率,范围为(0,1],其原理为在距离玩家 (1 / volume -1) 的地方播放音频
# speed: float
# 速度倍率,注意:这里的速度指的是播放速度倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
# progressbar: bool|tuple[str, Tuple[str,]]
# 进度条,当此参数为 `True` 时使用默认进度条,为其他的**值为真**的参数时识别为进度条自定义参数,为其他**值为假**的时候不生成进度条
def __init__(
self,
output_path: str,
volume: float = 1.0,
speed: float = 1.0,
progressbar: Union[bool, Tuple[str, Tuple[str, str]]] = True,
):
"""
将已经转换好的数据内容指令载入MC可读格式
# """
Parameters
----------
output_path: str
生成内容的输出目录
volume: float
音量比率,范围为(0,1],其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度倍率,注意:这里的速度指的是播放速度倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
progressbar: bool|tuple[str, Tuple[str,]]
进度条,当此参数为 `True` 时使用默认进度条,为其他的**值为真**的参数时识别为进度条自定义参数,为其他**值为假**的时候不生成进度条
# self.dist_path = output_path
# """输出目录"""
"""
# if progressbar:
# # 此处是对于仅有 True 的参数和自定义参数的判断
# # 改这一段没🐎
# if progressbar is True:
# self.progressbar_style = DEFAULT_PROGRESSBAR_STYLE
# """进度条样式"""
# return
# elif isinstance(progressbar, ProgressBarStyle):
# self.progressbar_style = progressbar
# """进度条样式"""
# return
# elif isinstance(progressbar, tuple):
# if isinstance(progressbar[0], str) and isinstance(
# progressbar[1], tuple
# ):
# if isinstance(progressbar[1][0], str) and isinstance(
# progressbar[1][1], str
# ):
# self.progressbar_style = ProgressBarStyle(
# progressbar[0], progressbar[1][0], progressbar[1][1]
# )
# return
# if not ignore_progressbar_param_error:
# raise TypeError(
# "参数 {} 的类型 {} 与所需类型 Union[bool, Tuple[str, Tuple[str, str]], ProgressBarStyle] 不符。".format(
# progressbar, type(progressbar)
# )
# )
self.dist_path = output_path
"""输出目录"""
self.volume_ratio = volume
"""音量比例"""
self.speed_multiplier = speed
"""速度倍率"""
if progressbar:
# 此处是对于仅有 True 的参数和自定义参数的判断
# 改这一段没🐎
if progressbar is True:
self.progressbar_style = DEFAULT_PROGRESSBAR_STYLE
"""进度条样式组"""
else:
self.progressbar_style = progressbar
"""进度条样式组"""
else:
self.progressbar_style = None
"""进度条样式组"""
# self.progressbar_style = None
# """进度条样式组"""

View File

@@ -2,8 +2,8 @@
"""
用以生成单个mcstructure文件的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -17,7 +17,14 @@ Terms & Conditions: License.md in the root directory
__all__ = [
"to_mcstructure_file_in_delay",
"to_mcstructure_file_in_repeater",
"to_mcstructure_file_in_score",
"to_mcstructure_files_in_repeater_divided_by_instruments",
]
__author__ = (("金羿", "Eilles Wan"),)
__author__ = (("金羿", "Eilles"),)
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,
to_mcstructure_file_in_score,
to_mcstructure_files_in_repeater_divided_by_instruments,
)

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -14,9 +14,8 @@ Terms & Conditions: License.md in the root directory
import os
from typing import Literal
from ...exceptions import CommandFormatError
from ...main import MidiConvert
from ..main import ConvertConfig
from ...subclass import MineCommand
from ..mcstructure import (
COMPABILITY_VERSION_117,
COMPABILITY_VERSION_119,
@@ -27,7 +26,7 @@ from ..mcstructure import (
def to_mcstructure_file_in_delay(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
dist_path: str,
player: str = "@a",
max_height: int = 64,
):
@@ -38,8 +37,8 @@ def to_mcstructure_file_in_delay(
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
max_height: int
@@ -47,7 +46,7 @@ def to_mcstructure_file_in_delay(
Returns
-------
tuple[tuple[int,]结构大小, int音乐总延迟]
tuple[tuple[int,int,int]结构大小, int音乐总延迟]
"""
compability_ver = (
@@ -57,22 +56,18 @@ def to_mcstructure_file_in_delay(
)
cmd_list, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
struct, size, end_pos = commands_to_structure(
cmd_list, max_height - 1, compability_version_=compability_ver
)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.mcstructure")
),
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[delay].mcstructure")),
"wb+",
) as f:
struct.dump(f)
@@ -80,9 +75,80 @@ def to_mcstructure_file_in_delay(
return size, max_delay
def to_mcstructure_file_in_score(
midi_cvt: MidiConvert,
dist_path: str,
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
max_height: int = 64,
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
是否自动重置计分板
max_height: int
生成结构最大高度
Returns
-------
tuple[tuple[int,int,int]结构大小, int音乐总延迟, int指令数量
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
cmd_list, cmd_count, max_delay = midi_cvt.to_command_list_in_score(
scoreboard_name=scoreboard_name,
)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
struct, size, end_pos = commands_to_structure(
midi_cvt.music_command_list
+ (
[
MineCommand(
command="scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(max_delay + 20)
+ "}] "
+ scoreboard_name,
annotation="自动重置计分板",
)
]
if auto_reset
else []
),
max_height - 1,
compability_version_=compability_ver,
)
with open(
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[score].mcstructure")),
"wb+",
) as f:
struct.dump(f)
return size, max_delay, cmd_count
def to_mcstructure_file_in_repeater(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
dist_path: str,
player: str = "@a",
axis_side: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
basement_block: str = "concrete",
@@ -94,8 +160,8 @@ def to_mcstructure_file_in_repeater(
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
axis_side: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"]
@@ -105,7 +171,7 @@ def to_mcstructure_file_in_repeater(
Returns
-------
tuple[tuple[int,]结构大小, int音乐总延迟]
tuple[tuple[int,int,int]结构大小, int音乐总延迟]
"""
compability_ver = (
@@ -115,13 +181,11 @@ def to_mcstructure_file_in_repeater(
)
cmd_list, max_delay, max_multiple_cmd = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
player_selector=player,
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
struct, size, end_pos = commands_to_redstone_delay_structure(
cmd_list,
@@ -133,11 +197,102 @@ def to_mcstructure_file_in_repeater(
)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.mcstructure")
),
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[repeater].mcstructure")),
"wb+",
) as f:
struct.dump(f)
return size, max_delay
def to_mcstructure_files_in_repeater_divided_by_instruments(
midi_cvt: MidiConvert,
dist_path: str,
player: str = "@a",
axis_side: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
basement_block: str = "concrete",
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
axis_side: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"]
生成结构的延展方向
basement_block: str
结构的基底方块
Returns
-------
int音乐总延迟
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
cmd_dict, max_delay, max_multiple_cmd_count = (
midi_cvt.to_command_list_in_delay_devided_by_instrument(
player_selector=player,
)
)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
for inst, cmd_list in cmd_dict.items():
struct, size, end_pos = commands_to_redstone_delay_structure(
cmd_list,
max_delay,
max_multiple_cmd_count[inst],
basement_block,
axis_side,
compability_version_=compability_ver,
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"{}[repeater-div]_{}.mcstructure".format(
midi_cvt.music_name, inst.replace(".", "-")
),
)
),
"wb+",
) as f:
struct.dump(f)
return max_delay
def to_mcstructure_file_in_blocks(
midi_cvt: MidiConvert,
dist_path: str,
player: str = "@a",
):
"""
将midi以方块形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
Returns
-------
int音乐总延迟
"""
pass

View File

@@ -4,8 +4,8 @@
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -21,7 +21,7 @@ from typing import List, Literal, Tuple
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
from ..constants import x, y, z
from ..subclass import SingleCommand
from ..subclass import MineCommand
from .common import bottem_side_length_of_smallest_square_bottom_box
@@ -58,6 +58,10 @@ COMPABILITY_VERSION_117: int = 17879555
"""
Minecraft 1.17 兼容版本号
"""
COMPABILITY_VERSION_121: int = 18168865
"""
Minecraft 1.21 兼容版本号
"""
def command_statevalue(axis_: Literal["x", "y", "z", "X", "Y", "Z"], forward_: bool):
@@ -70,17 +74,25 @@ def form_note_block_in_NBT_struct(
instrument: str = "note.harp",
powered: bool = False,
compability_version_number: int = COMPABILITY_VERSION_119,
):
"""生成音符盒方块
:param note: `int`(0~24)
) -> Block:
"""
生成音符盒方块
Parameters
------------
note: int (0~24)
音符的音高
:param coordinate: `tuple[int,int,int]`
coordinate: tuple[int, int, int]
此方块所在之相对坐标
:param instrument: `str`
instrument: str
音符盒的乐器
:param powered: `bool`
powered: bool
是否已被激活
:return Block
Returns
-------
Block
生成的方块对象
"""
return Block(
@@ -108,15 +120,26 @@ def form_repeater_in_NBT_struct(
delay: int,
facing: int,
compability_version_number: int = COMPABILITY_VERSION_119,
):
"""生成中继器方块
:param facing: 朝向:
) -> Block:
"""
生成中继器方块
Parameters
----------
facing: int (0~3)
朝向:
Z- 北 0
X- 东 1
Z+ 南 2
X+ 西 3
:param delay: 0~3
:return Block()"""
delay: int (0~3)
信号延迟
Returns
-------
Block
生成的方块对象
"""
return Block(
"minecraft",
@@ -141,57 +164,67 @@ def form_command_block_in_NBT_struct(
executeOnFirstTick: bool = False,
trackOutput: bool = True,
compability_version_number: int = COMPABILITY_VERSION_119,
):
) -> Block:
"""
使用指定项目返回指定的指令方块结构
:param command: `str`
使用指定参数生成指令方块
Parameters
----------
command: str
指令
:param coordinate: `tuple[int,int,int]`
coordinate: tuple[int,int,int]
此方块所在之相对坐标
:param particularValue:
particularValue: int
方块特殊值,即朝向
:0 下 无条件
:1 上 无条件
:2 z轴负方向 无条件
:3 z轴正方向 无条件
:4 x轴负方向 无条件
:5 x轴正方向 无条件
:6 下 无条件
:7 下 无条件
:8 下 有条件
:9 上 有条件
:10 z轴方向 有条件
:11 z轴正方向 有条件
:12 x轴方向 有条件
:13 x轴正方向 有条件
:14 下 有条件
:14 下 有条件
:0 下 无条件
:1 上 无条件
:2 z轴负方向 无条件
:3 z轴正方向 无条件
:4 x轴负方向 无条件
:5 x轴正方向 无条件
:6 下 无条件
:7 下 无条件
:8 下 有条件
:9 上 有条件
:10 z轴负方向 有条件
:11 z轴方向 有条件
:12 x轴负方向 有条件
:13 x轴方向 有条件
:14 下 有条件
:14 下 有条件
注意此处特殊值中的条件会被下面condition参数覆写
:param impluse: `int 0|1|2`
impluse: int (0|1|2)
方块类型
0脉冲 1循环 2连锁
:param condition: `bool`
0脉冲 1循环 2连锁
condition: bool
是否有条件
:param alwaysRun: `bool`
alwaysRun: bool
是否始终执行
:param tickDelay: `int`
tickDelay: int
执行延时
:param customName: `str`
customName: str
悬浮字
:param executeOnFirstTick: `bool`
首刻执行(循环指令方块是否激活后立即执行若为False则从激活时起延迟后第一次执行)
:param trackOutput: `bool`
是否输出
executeOnFirstTick: bool
是否启用首刻执行循环指令方块是否激活后立即执行若为False则从激活时起延迟后第一次执行
trackOutput: bool
是否启用命令方块输出
compability_version_number: int
版本兼容代号
:return:str
Returns
-------
Block
生成的方块对象
"""
return Block(
"minecraft",
"command_block"
if impluse == 0
else ("repeating_command_block" if impluse == 1 else "chain_command_block"),
(
"command_block"
if impluse == 0
else ("repeating_command_block" if impluse == 1 else "chain_command_block")
),
states={"conditional_bit": condition, "facing_direction": particularValue},
extra_data={
"block_entity_data": {
@@ -207,7 +240,9 @@ def form_command_block_in_NBT_struct(
"SuccessCount": 0,
"TickDelay": tickDelay,
"TrackOutput": trackOutput,
"Version": 25,
"Version": (
25 if compability_version_number <= COMPABILITY_VERSION_119 else 43
),
"auto": alwaysRun,
"conditionMet": False, # 是否已经满足条件
"conditionalMode": condition,
@@ -224,14 +259,24 @@ def form_command_block_in_NBT_struct(
def commands_to_structure(
commands: List[SingleCommand],
commands: List[MineCommand],
max_height: int = 64,
compability_version_: int = COMPABILITY_VERSION_119,
):
"""
:param commands: 指令列表
:param max_height: 生成结构最大高度
:return 结构类,结构占用大小,终点坐标
由指令列表生成(纯指令方块)结构
Parameters
------------
commands: list
指令列表
max_height: int
生成结构最大高度
Returns
---------
Structure, tuple[int, int, int], tuple[int, int, int]
结构类, 结构占用大小, 终点坐标
"""
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
@@ -257,18 +302,20 @@ def commands_to_structure(
form_command_block_in_NBT_struct(
command=command.command_text,
coordinate=coordinate,
particularValue=(1 if y_forward else 0)
if (
((now_y != 0) and (not y_forward))
or (y_forward and (now_y != (max_height - 1)))
)
else (
(3 if z_forward else 2)
particularValue=(
(1 if y_forward else 0)
if (
((now_z != 0) and (not z_forward))
or (z_forward and (now_z != _sideLength - 1))
((now_y != 0) and (not y_forward))
or (y_forward and (now_y != (max_height - 1)))
)
else (
(3 if z_forward else 2)
if (
((now_z != 0) and (not z_forward))
or (z_forward and (now_z != _sideLength - 1))
)
else 5
)
else 5
),
impluse=2,
condition=False,
@@ -309,7 +356,7 @@ def commands_to_structure(
def commands_to_redstone_delay_structure(
commands: List[SingleCommand],
commands: List[MineCommand],
delay_length: int,
max_multicmd_length: int,
base_block: str = "concrete",
@@ -317,12 +364,25 @@ def commands_to_redstone_delay_structure(
compability_version_: int = COMPABILITY_VERSION_119,
) -> 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 结构类,结构占用大小,终点坐标
由指令列表生成由红石中继器延迟的结构
Parameters
------------
commands: list
指令列表
delay_length: int
延时总长
max_multicmd_length: int
最大同时播放的音符数量
base_block: Block
生成结构的基底方块
axis_: str
生成结构的延展方向
Returns
---------
Structure, tuple[int, int, int], tuple[int, int, int]
结构类, 结构占用大小, 终点坐标
"""
if axis_ in ["z+", "Z+"]:
extensioon_direction = z
@@ -357,20 +417,16 @@ def commands_to_redstone_delay_structure(
for cmd in commands:
# print("\r 正在进行处理:",end="")
if cmd.delay > 2:
a_max = max(a,a_max)
a_max = max(a, a_max)
total_cmd += (a := 1)
else:
a += 1
struct = Structure(
size=(
round(delay_length / 2 + total_cmd)
if extensioon_direction == x
else a_max,
round(delay_length / 2 + total_cmd) if extensioon_direction == x else a_max,
3,
round(delay_length / 2 + total_cmd)
if extensioon_direction == z
else a_max,
round(delay_length / 2 + total_cmd) if extensioon_direction == z else a_max,
),
fill=Block("minecraft", "air", compability_version=compability_version_),
compability_version=compability_version_,

View File

@@ -4,8 +4,8 @@
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -18,8 +18,8 @@ Terms & Conditions: License.md in the root directory
from ..exceptions import NotDefineProgramError, ZeroSpeedError
from ..main import MidiConvert
from ..subclass import SingleCommand
from ..utils import inst_to_souldID_withX, perc_inst_to_soundID_withX
from ..subclass import MineCommand
from ..utils import inst_to_sould_with_deviation, perc_inst_to_soundID_withX
# 你以为写完了吗?其实并没有
@@ -71,13 +71,13 @@ def to_note_list(
soundID, _X = (
perc_inst_to_soundID_withX(InstID)
if SpecialBits
else inst_to_souldID_withX(InstID)
else inst_to_sould_with_deviation(InstID)
)
except UnboundLocalError as E:
soundID, _X = (
perc_inst_to_soundID_withX(-1)
if SpecialBits
else inst_to_souldID_withX(-1)
else inst_to_sould_with_deviation(-1)
)
score_now = round(msg[-1] / float(speed) / 50)
# print(score_now)

View File

@@ -4,8 +4,8 @@
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -15,4 +15,4 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import nbtschematic
# import nbtschematic

View File

@@ -2,8 +2,8 @@
"""
用以生成Schematic结构的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -16,7 +16,7 @@ Terms & Conditions: License.md in the root directory
__all__ = [
]
__author__ = (("金羿", "Eilles Wan"),)
__author__ = (("金羿", "Eilles"),)
from .main import *

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory

View File

@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关WebSocket服务器操作的内容
"""
"""
版权所有 © 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 fcwslib
# 这个库有问题,正在检修
class Plugin(fcwslib.Plugin):
async def on_connect(self) -> None:
print("对象已被连接")
await self.send_command("list", callback=self.list)
await self.subscribe("PlayerMessage", callback=self.player_message)
async def on_disconnect(self) -> None:
print("对象停止连接")
async def on_receive(self, response) -> None:
print("已接收非常规回复 {}".format(response))
async def list(self, response) -> None:
print("已收取指令执行回复 {}".format(response))
async def player_message(self, response) -> None:
print("已收取玩家事件回复 {}".format(response))

View File

@@ -2,8 +2,8 @@
"""
用以启动WebSocket服务器播放的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -15,7 +15,7 @@ Terms & Conditions: License.md in the root directory
__all__ = []
__author__ = (("金羿", "Eilles Wan"),)
__author__ = (("金羿", "Eilles"),)
from .main import *

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -11,19 +11,132 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import asyncio
import time
import uuid
from typing import List, Literal, Optional, Tuple
import fcwslib
from ...main import MidiConvert
from ..main import ConvertConfig
from ...subclass import SingleCommand
from ...subclass import MineCommand, ProgressBarStyle
def open_websocket_server(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
server_dist: str = "localhost",
server_port: int = 8000,
):
wssever = fcwslib.Server(server=server_dist,port=server_port,debug_mode=False)
def to_websocket_server(
midi_cvt_lst: List[MidiConvert],
server_dist: str,
server_port: int,
progressbar_style: Optional[ProgressBarStyle],
) -> None:
"""
将midi以延迟播放器形式转换为mcstructure结构文件后打包成附加包并在附加包中生成相应地导入函数
Parameters
----------
midi_cvt: List[MidiConvert]
一组用于转换的MidiConvert对象
server_dist: str
WebSocket播放服务器开启地址
server_port: str
WebSocket播放服务器开启端口
progressbar_style: ProgressBarStyle 对象
进度条对象
Returns
-------
None
"""
replacement = str(uuid.uuid4())
musics = dict(
[
(k.music_name, k.to_command_list_in_delay(replacement)[:2])
for k in midi_cvt_lst
]
)
class Plugin(fcwslib.Plugin):
async def on_connect(self) -> None:
print("已成功获连接")
await self.send_command("list", callback=self.cmd_feedback)
await self.subscribe("PlayerMessage", callback=self.player_message)
async def on_disconnect(self) -> None:
print("连接已然终止")
await self.disconnect()
async def on_receive(self, response) -> None:
print("已收取非已知列回复 {}".format(response))
async def cmd_feedback(self, response) -> None:
print("已收取指令执行回复 {}".format(response))
async def player_message(self, response) -> None:
print("已收取玩家事件回复 {}".format(response))
if response["body"]["message"].startswith(("。播放", ".play")):
whom_to_play: str = response["body"]["sender"]
music_to_play: str = (
response["body"]["message"]
.replace("。播放", "")
.replace(".play", "")
.strip()
)
if music_to_play in musics.keys():
self.check_play = True
delay_of_now = 0
now_played_cmd = 0
_time = time.time()
for i in range(musics[music_to_play][1]):
if not self.check_play:
break
await asyncio.sleep((0.05 - (time.time() - _time)) % 0.05)
_time = time.time()
if progressbar_style:
await self.send_command(
"title {} actionbar {}".format(
whom_to_play,
progressbar_style.play_output(
played_delays=i,
total_delays=musics[music_to_play][1],
music_name=music_to_play,
),
),
callback=self.cmd_feedback,
)
delay_of_now += 1
if (
delay_of_now
>= (cmd := musics[music_to_play][0][now_played_cmd]).delay
):
await self.send_command(
cmd.command_text.replace(replacement, whom_to_play),
callback=self.cmd_feedback,
)
now_played_cmd += 1
delay_of_now = 0
else:
await self.send_command(
"tellraw {} {}{}{}".format(
whom_to_play,
r'{"rawtext":[{"text":"§c§l所选歌曲',
music_to_play,
'无法播放:播放列表不存在之"}]}',
),
callback=self.cmd_feedback,
)
elif response["body"]["message"].startswith(
("。停止播放", ".stopplay", ".stoplay")
):
self.check_play = False
elif response["body"]["message"].startswith(
("。终止连接", ".terminate", ".endconnection")
):
await self.disconnect()
server = fcwslib.Server(server=server_dist, port=server_port, debug_mode=True)
server.add_plugin(Plugin)
asyncio.run(server.run_forever())

View File

@@ -1,455 +0,0 @@
# -*- 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 .exceptions import *
from .main import MidiConvert, mido
from .subclass import *
from .types import ChannelType
from .utils import *
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(
self,
scoreboard_name: str = "mscplay",
MaxVolume: float = 1.0,
speed: float = 1.0,
) -> list:
"""
使用Dislink Sforza的转换思路将midi转换为我的世界命令列表
:param scoreboard_name: 我的世界的计分板名称
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:return: tuple(命令列表, 命令个数, 计分板最大值)
"""
# :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
tracks = []
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
if not self.midi:
raise MidiUnboundError(
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象这是不可复用的。"
)
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
commands = 0
maxscore = 0
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
# 分轨的思路其实并不好,但这个算法就是这样
# 所以我建议用第二个方法 _toCmdList_m2
for i, track in enumerate(self.midi.tracks):
ticks = 0
instrumentID = 0
singleTrack = []
for msg in track:
ticks += msg.time
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
else:
if msg.type == "program_change":
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)
if msg.channel == 9:
soundID, _X = perc_inst_to_soundID_withX(instrumentID)
else:
soundID, _X = inst_to_souldID_withX(instrumentID)
singleTrack.append(
"execute @a[scores={"
+ str(scoreboard_name)
+ "="
+ str(nowscore)
+ "}"
+ f"] ~ ~ ~ playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
f"{2 ** ((msg.note - 60 - _X) / 12)}"
)
commands += 1
if len(singleTrack) != 0:
tracks.append(singleTrack)
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): # type:ignore
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) # type: ignore
)
maxscore = max(maxscore, nowscore)
soundID, _X = inst_to_souldID_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(
self,
scoreboard_name: str = "mscplay",
MaxVolume: float = 1.0,
speed: float = 1.0,
) -> tuple:
"""
使用神羽和金羿的转换思路将midi转换为我的世界命令列表
:param scoreboard_name: 我的世界的计分板名称
:param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:return: tuple(命令列表, 命令个数, 计分板最大值)
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
tracks = []
cmdAmount = 0
maxScore = 0
InstID = -1
self.to_music_channels()
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
if i == 9:
SpecialBits = True
else:
SpecialBits = False
nowTrack = []
for track_no, track in self.channels[i].items(): # type: ignore
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else inst_to_souldID_withX(InstID)
)
score_now = round(msg[-1] / float(speed) / 50)
maxScore = max(maxScore, score_now)
nowTrack.append(
self.execute_cmd_head.format(
"@a[scores=({}={})]".format(scoreboard_name, score_now)
.replace("(", r"{")
.replace(")", r"}")
)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
cmdAmount += 1
if nowTrack:
tracks.append(nowTrack)
return tracks, cmdAmount, maxScore
def _toCmdList_withDelay_m1(
self,
MaxVolume: float = 1.0,
speed: float = 1.0,
player: str = "@a",
) -> list:
"""
使用Dislink Sforza的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟
:param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:param player: 玩家选择器,默认为`@a`
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
"""
tracks = {}
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
if not self.midi:
raise MidiUnboundError(
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象这是不可复用的。"
)
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
for i, track in enumerate(self.midi.tracks):
instrumentID = 0
ticks = 0
for msg in track:
ticks += msg.time
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
else:
if msg.type == "program_change":
instrumentID = msg.program
if msg.type == "note_on" and msg.velocity != 0:
now_tick = round(
(ticks * tempo)
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
)
soundID, _X = inst_to_souldID_withX(instrumentID)
try:
tracks[now_tick].append(
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
f"{2 ** ((msg.note - 60 - _X) / 12)}"
)
except KeyError:
tracks[now_tick] = [
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
f"{2 ** ((msg.note - 60 - _X) / 12)}"
]
results = []
all_ticks = list(tracks.keys())
all_ticks.sort()
for i in range(len(all_ticks)):
if i != 0:
for j in range(len(tracks[all_ticks[i]])):
if j != 0:
results.append((tracks[all_ticks[i]][j], 0))
else:
results.append(
(tracks[all_ticks[i]][j], all_ticks[i] - all_ticks[i - 1])
)
else:
for j in range(len(tracks[all_ticks[i]])):
results.append((tracks[all_ticks[i]][j], all_ticks[i]))
return [results, max(all_ticks)]
def _toCmdList_withDelay_m2(
self,
MaxVolume: float = 1.0,
speed: float = 1.0,
player: str = "@a",
) -> list:
"""
使用神羽和金羿的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟
:param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:param player: 玩家选择器,默认为`@a`
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
"""
tracks = {}
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
InstID = -1
self.to_music_channels()
results = []
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
if i == 9:
SpecialBits = True
else:
SpecialBits = False
for track_no, track in self.channels[i].items(): # type: ignore
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else inst_to_souldID_withX(InstID)
)
score_now = round(msg[-1] / float(speed) / 50)
try:
tracks[score_now].append(
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
except KeyError:
tracks[score_now] = [
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
]
all_ticks = list(tracks.keys())
all_ticks.sort()
for i in range(len(all_ticks)):
for j in range(len(tracks[all_ticks[i]])):
results.append(
(
tracks[all_ticks[i]][j],
(
0
if j != 0
else (
all_ticks[i] - all_ticks[i - 1]
if i != 0
else all_ticks[i]
)
),
)
)
return [results, max(all_ticks)]

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
"""
存储许多非主要的相关
存储音·创附属子
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -16,128 +16,480 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from math import sin, cos, asin, radians, degrees, sqrt, atan
from dataclasses import dataclass
from typing import Optional
from typing import Optional, Any, List, Tuple, Union, Dict, Sequence
from .constants import PERCUSSION_INSTRUMENT_LIST
from .utils import inst_to_souldID_withX, perc_inst_to_soundID_withX, volume2distance
from .constants import MC_PITCHED_INSTRUMENT_LIST
@dataclass(init=False)
class SingleNote:
class MineNote:
"""存储单个音符的类"""
instrument: int
"""乐器编号"""
sound_name: str
"""乐器ID"""
note: int
"""音符编号"""
note_pitch: int
"""midi音高"""
velocity: int
"""力度/响度"""
"""力度"""
start_time: int
"""开始之时 ms"""
start_tick: int
"""开始之时 命令刻"""
duration: int
"""音符持续时间 ms"""
"""音符持续时间 命令刻"""
track_no: int
"""音符所处的音轨"""
high_precision_time: int
"""高精度开始时间偏量 1/1250 秒"""
percussive: bool
"""是否为打击乐器"""
"""是否为打击乐器启用"""
sound_distance: float
"""声源距离 方块"""
sound_azimuth: Tuple[float, float]
"""声源方位 角度"""
extra_info: Dict[str, Any]
"""你觉得放什么好?"""
def __init__(
self,
instrument: int,
pitch: int,
velocity: int,
startime: int,
lastime: int,
track_number: int = 0,
mc_sound_name: str,
midi_pitch: Optional[int],
midi_velocity: int,
start_time: int,
last_time: int,
mass_precision_time: int = 0,
is_percussion: Optional[bool] = None,
distance: Optional[float] = None,
azimuth: Optional[Tuple[float, float]] = None,
extra_information: Dict[str, Any] = {},
):
"""用于存储单个音符的类
:param instrument 乐器编号
:param pitch 音符编号
:param velocity 力度/响度
:param startTime 开始之时(ms)
注:此处的时间是用从乐曲开始到当前的毫秒数
:param lastTime 音符延续时间(ms)"""
self.instrument: int = instrument
"""乐器编号"""
self.note: int = pitch
"""音符编号"""
self.velocity: int = velocity
"""力度/响度"""
self.start_time: int = startime
"""开始之时 ms"""
self.duration: int = lastime
"""音符持续时间 ms"""
self.track_no: int = track_number
"""音符所处的音轨"""
"""
用于存储单个音符的类
Parameters
------------
mc_sound_name: str
《我的世界》声音ID
midi_pitch: int
midi音高
midi_velocity: int
midi响度(力度)
start_time: int
开始之时(命令刻)
注:此处的时间是用从乐曲开始到当前的刻数
last_time: int
音符延续时间(命令刻)
mass_precision_time: int
高精度的开始时间偏移量(1/1250秒)
is_percussion: bool
是否作为打击乐器
distance: float
发声源距离玩家的距离(半径 `r`
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
azimuth: tuple[float, float]
声源方位
此参数为tuple包含两个元素分别表示
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
extra_information: Dict[str, Any]
附加信息,尽量存储为字典
Returns
---------
MineNote 类
"""
self.sound_name: str = mc_sound_name
"""乐器ID"""
self.note_pitch: int = 66 if midi_pitch is None else midi_pitch
"""midi音高"""
self.velocity: int = midi_velocity
"""响度(力度)"""
self.start_tick: int = start_time
"""开始之时 命令刻"""
self.duration: int = last_time
"""音符持续时间 命令刻"""
self.high_precision_time: int = mass_precision_time
"""高精度开始时间偏量 0.4 毫秒"""
self.percussive = (
(is_percussion in PERCUSSION_INSTRUMENT_LIST)
(mc_sound_name not in MC_PITCHED_INSTRUMENT_LIST)
if (is_percussion is None)
else is_percussion
)
"""是否为打击乐器"""
@property
def inst(self) -> int:
"""乐器编号"""
return self.instrument
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
"""声源方位"""
@inst.setter
def inst(self, inst_: int):
self.instrument = inst_
# 如果指定为零,那么为零,但如果不指定或者指定为负数,则为 0.01 的距离
self.sound_distance = (
(16 if distance > 16 else (distance if distance >= 0 else 0.01))
if distance is not None
else 0.01
)
"""声源距离"""
self.extra_info = extra_information if extra_information else {}
@classmethod
def from_traditional(
cls,
mc_sound_name: str,
midi_pitch: Optional[int],
midi_velocity: int,
start_time: int,
last_time: int,
mass_precision_time: int = 0,
is_percussion: Optional[bool] = None,
displacement: Optional[Tuple[float, float, float]] = None,
extra_information: Optional[Any] = None,
):
"""
从传统音像位移格式传参,写入用于存储单个音符的类
Parameters
------------
mc_sound_name: str
《我的世界》声音ID
midi_pitch: int
midi音高
midi_velocity: int
midi响度(力度)
start_time: int
开始之时(命令刻)
注:此处的时间是用从乐曲开始到当前的刻数
last_time: int
音符延续时间(命令刻)
mass_precision_time: int
高精度的开始时间偏移量(1/1250秒)
is_percussion: bool
是否作为打击乐器
displacement: tuple[float, float, float]
声像位移
extra_information: Any
附加信息,尽量为字典。
Returns
---------
MineNote 类
"""
if displacement is None:
displacement = (0, 0, 0)
r = 0
alpha_v = 0
beta_h = 0
else:
r = sqrt(displacement[0] ** 2 + displacement[1] ** 2 + displacement[2] ** 2)
if r == 0:
alpha_v = 0
beta_h = 0
else:
beta_h = round(degrees(asin(displacement[1] / r)), 8)
if displacement[2] == 0:
alpha_v = -90 if displacement[0] > 0 else 90
else:
alpha_v = round(
degrees(atan(-displacement[0] / displacement[2])), 8
)
return cls(
mc_sound_name=mc_sound_name,
midi_pitch=midi_pitch,
midi_velocity=midi_velocity,
start_time=start_time,
last_time=last_time,
mass_precision_time=mass_precision_time,
is_percussion=is_percussion,
distance=r,
azimuth=(alpha_v, beta_h),
extra_information=(
(
extra_information
if isinstance(extra_information, dict)
else {"EXTRA_INFO": extra_information}
)
if extra_information
else {}
),
)
@property
def pitch(self) -> int:
"""音符编号"""
return self.note
def position_displacement(self) -> Tuple[float, float, float]:
"""声像位移"""
dk1 = self.sound_distance * round(cos(radians(self.sound_azimuth[1])), 8)
return (
-dk1 * round(sin(radians(self.sound_azimuth[0])), 8),
self.sound_distance * round(sin(radians(self.sound_azimuth[1])), 8),
dk1 * round(cos(radians(self.sound_azimuth[0])), 8),
)
@property
def get_mc_pitch(self) -> float:
self.mc_sound_ID, _X = (
perc_inst_to_soundID_withX(self.inst)
@classmethod
def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
"""自字节码析出 MineNote 类"""
group_1 = int.from_bytes(code_buffer[:6], "big")
percussive_ = bool(group_1 & 0b1)
duration_ = (group_1 := group_1 >> 1) & 0b11111111111111111
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
note_pitch_ = (group_1 := group_1 >> 17) & 0b1111111
sound_name_length = group_1 >> 7
if code_buffer[6] & 0b1:
distance_ = (
code_buffer[8 + sound_name_length]
if is_high_time_precision
else code_buffer[7 + sound_name_length]
) / 15
group_2 = int.from_bytes(
(
code_buffer[9 + sound_name_length : 14 + sound_name_length]
if is_high_time_precision
else code_buffer[8 + sound_name_length : 13 + sound_name_length]
),
"big",
)
azimuth_ = ((group_2 >> 20) / 2912, (group_2 & 0xFFFFF) / 2912)
else:
distance_ = 0
azimuth_ = (0, 0)
try:
return cls(
mc_sound_name=(
o := (
code_buffer[8 : 8 + sound_name_length]
if is_high_time_precision
else code_buffer[7 : 7 + sound_name_length]
)
).decode(encoding="GB18030"),
midi_pitch=note_pitch_,
midi_velocity=code_buffer[6] >> 1,
start_time=start_tick_,
last_time=duration_,
mass_precision_time=code_buffer[7] if is_high_time_precision else 0,
is_percussion=percussive_,
distance=distance_,
azimuth=azimuth_,
)
except:
print(code_buffer, "\n", o)
raise
def encode(
self, is_displacement_included: bool = True, is_high_time_precision: bool = True
) -> bytes:
"""
将数据打包为字节码
Parameters
------------
is_displacement_included: bool
是否包含声像偏移数据,默认为**是**
is_high_time_precision: bool
是否启用高精度,默认为**是**
Returns
---------
bytes
打包好的字节码
"""
# MineNote 的字节码共有三个顺次版本分别如下
# 字符串长度 6 位 支持到 63
# note_pitch 7 位 支持到 127
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# percussive 长度 1 位 支持到 1
# 共 48 位 合 6 字节
# +++
# velocity 长度 7 位 支持到 127
# is_displacement_included 长度 1 位 支持到 1
# 共 8 位 合 1 字节
# +++
# (在第二版中已舍弃)
# track_no 长度 8 位 支持到 255 合 1 字节
# (在第二版中新增)
# high_time_precision可选长度 8 位 支持到 255 合 1 字节 支持 1/1250 秒
# +++
# sound_name 长度最多 63 支持到 31 个中文字符 或 63 个西文字符
# 第一版编码: UTF-8
# 第二版编码: GB18030
# +++
# (在第三版中已废弃)
# position_displacement 每个元素长 16 位 合 2 字节
# 共 48 位 合 6 字节 支持存储三位小数和两位整数,其值必须在 [0, 65.535] 之间
# (在第三版中新增)
# sound_distance 8 位 支持到 255 即 16 格 合 1 字节(按值放大 15 倍存储,精度可达 1 / 15
# sound_azimuth 每个元素长 20 位 共 40 位 合 5 字节。每个值放大 2912 倍存储,即支持到 360.08756868131866 度,精度同理
return (
(
(
(
(
(
(
(
(
len(
r := self.sound_name.encode(
encoding="GB18030"
)
)
<< 7
)
+ self.note_pitch
)
<< 17
)
+ self.start_tick
)
<< 17
)
+ self.duration
)
<< 1
)
+ self.percussive
).to_bytes(6, "big")
+ ((self.velocity << 1) + is_displacement_included).to_bytes(1, "big")
# + self.track_no.to_bytes(1, "big")
+ (
self.high_precision_time.to_bytes(1, "big")
if is_high_time_precision
else b""
)
+ r
+ (
(
round(self.sound_distance * 15).to_bytes(1, "big")
+ (
(round(self.sound_azimuth[0] * 2912) << 20)
+ round(self.sound_azimuth[1] * 2912)
).to_bytes(5, "big")
)
if is_displacement_included
else b""
)
)
def set_info(self, key: Union[str, Sequence[str]], value: Any):
"""设置附加信息"""
if isinstance(key, str):
self.extra_info[key] = value
elif (
isinstance(key, Sequence)
and isinstance(value, Sequence)
and (k := len(key)) == len(value)
):
for i in range(k):
self.extra_info[key[i]] = value[i]
else:
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
def get_info(self, key: str) -> Any:
"""获取附加信息"""
if key in self.extra_info:
return self.extra_info[key]
elif "EXTRA_INFO" in self.extra_info:
if (
isinstance(self.extra_info["EXTRA_INFO"], dict)
and key in self.extra_info["EXTRA_INFO"]
):
return self.extra_info["EXTRA_INFO"].get(key)
else:
return self.extra_info["EXTRA_INFO"]
else:
return None
def stringize(
self, include_displacement: bool = False, include_extra_data: bool = False
) -> str:
return (
"{}Note(Instrument = {}, {}Velocity = {}, StartTick = {}, Duration = {}{}".format(
"Percussive" if self.percussive else "",
self.sound_name,
"" if self.percussive else "NotePitch = {}, ".format(self.note_pitch),
self.velocity,
self.start_tick,
self.duration,
)
+ (
", SoundDistance = `r`{}, SoundAzimuth = (`αV`{}, `βH`{})".format(
self.sound_distance, *self.sound_azimuth
)
if include_displacement
else ""
)
+ (", ExtraData = {}".format(self.extra_info) if include_extra_data else "")
+ ")"
)
def tuplize(self, is_displacement: bool = False):
tuplized = self.__tuple__()
return tuplized[:-2] + (tuplized[-2:] if is_displacement else ())
def __list__(self) -> List:
return (
[
self.percussive,
self.sound_name,
self.velocity,
self.start_tick,
self.duration,
self.sound_distance,
self.sound_azimuth,
]
if self.percussive
else inst_to_souldID_withX(self.inst)
)
return -1 if self.percussive else 2 ** ((self.note - 60 - _X) / 12)
def __str__(self, is_track: bool = False):
return "{}Note(Instrument = {}, {}Velocity = {}, StartTime = {}, Duration = {}{})".format(
"Percussive" if self.percussive else "",
self.inst,
"" if self.percussive else "Pitch = {}, ".format(self.pitch),
self.start_time,
self.duration,
", Track = {}".format(self.track_no) if is_track else "",
else [
self.percussive,
self.sound_name,
self.note_pitch,
self.velocity,
self.start_tick,
self.duration,
self.sound_distance,
self.sound_azimuth,
]
)
def __tuple__(self):
def __tuple__(
self,
) -> Union[
Tuple[bool, str, int, int, int, int, float, Tuple[float, float]],
Tuple[bool, str, int, int, int, float, Tuple[float, float]],
]:
return (
(
self.percussive,
self.inst,
self.sound_name,
self.velocity,
self.start_time,
self.start_tick,
self.duration,
self.track_no,
self.sound_distance,
self.sound_azimuth,
)
if self.percussive
else (
self.percussive,
self.inst,
self.note,
self.sound_name,
self.note_pitch,
self.velocity,
self.start_time,
self.start_tick,
self.duration,
self.track_no,
self.sound_distance,
self.sound_azimuth,
)
)
@@ -145,57 +497,36 @@ class SingleNote:
return (
{
"Percussive": self.percussive,
"Instrument": self.inst,
"Instrument": self.sound_name,
"Velocity": self.velocity,
"StartTime": self.start_time,
"StartTick": self.start_tick,
"Duration": self.duration,
"Track": self.track_no,
"SoundDistance": self.sound_distance,
"SoundAzimuth": self.sound_azimuth,
"ExtraData": self.extra_info,
}
if self.percussive
else {
"Percussive": self.percussive,
"Instrument": self.inst,
"Pitch": self.note,
"Instrument": self.sound_name,
"Pitch": self.note_pitch,
"Velocity": self.velocity,
"StartTime": self.start_time,
"StartTick": self.start_tick,
"Duration": self.duration,
"Track": self.track_no,
"SoundDistance": self.sound_distance,
"SoundAzimuth": self.sound_azimuth,
"ExtraData": self.extra_info,
}
)
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()
def to_command(self, volume_percentage: float = 1) -> str:
"""
将音符转为播放的指令
:param volume_percentage:int 音量占比(0,1]
:return 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 = volume2distance(self.velocity * volume_percentage)
return "playsound {} @s ^ ^ ^{} {} {}".format(
self.mc_sound_ID,
self.mc_distance_volume,
volume_percentage,
self.mc_pitch,
)
return self.tuplize() == other.tuplize()
@dataclass(init=False)
class SingleCommand:
class MineCommand:
"""存储单个指令的类"""
command_text: str
@@ -237,7 +568,7 @@ class SingleCommand:
self.annotation_text = annotation
def copy(self):
return SingleCommand(
return MineCommand(
command=self.command_text,
condition=self.conditional,
tick_delay=self.delay,
@@ -255,8 +586,8 @@ class SingleCommand:
"""
转为我的世界函数文件格式(包含注释)
"""
return "#[{cdt}]<{delay}> {ant}\n{cmd}".format(
cdt="CDT" if self.conditional else "",
return "# {cdt}<{delay}> {ant}\n{cmd}".format(
cdt="[CDT]" if self.conditional else "",
delay=self.delay,
ant=self.annotation_text,
cmd=self.command_text,
@@ -291,12 +622,26 @@ class SingleNoteBox:
percussion: Optional[bool] = None,
annotation: str = "",
):
"""用于存储单个音符盒的类
:param instrument_block_ 音符盒演奏所使用的乐器方块
:param note_value_ 音符盒的演奏音高
:param percussion 此音符盒乐器是否作为打击乐处理
"""
用于存储单个音符盒的类
Parameters
------------
instrument_block_: str
音符盒演奏所使用的乐器方块
note_value_: int
音符盒的演奏音高
percussion: bool
此音符盒乐器是否作为打击乐处理
注:若为空,则自动识别是否为打击乐器
:param annotation 音符注释"""
annotation: Any
音符注释
Returns
---------
SingleNoteBox 类
"""
self.instrument_block = instrument_block_
"""乐器方块"""
self.note_value = note_value_
@@ -304,7 +649,7 @@ class SingleNoteBox:
self.annotation_text = annotation
"""音符注释"""
if percussion is None:
self.is_percussion = percussion in PERCUSSION_INSTRUMENT_LIST
self.is_percussion = percussion not in MC_PITCHED_INSTRUMENT_LIST
else:
self.is_percussion = percussion
@@ -359,3 +704,158 @@ class SingleNoteBox:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()
@dataclass(init=False)
class ProgressBarStyle:
"""进度条样式类"""
base_style: str
"""基础样式"""
to_play_style: str
"""未播放之样式"""
played_style: str
"""已播放之样式"""
def __init__(
self,
base_s: Optional[str] = None,
to_play_s: Optional[str] = None,
played_s: Optional[str] = None,
):
"""
用于存储进度条样式的类
| 标识符 | 指定的可变量 |
|---------|----------------|
| `%%N` | 乐曲名(即传入的文件名)|
| `%%s` | 当前计分板值 |
| `%^s` | 计分板最大值 |
| `%%t` | 当前播放时间 |
| `%^t` | 曲目总时长 |
| `%%%` | 当前进度比率 |
| `_` | 用以表示进度条占位|
Parameters
------------
base_s: str
基础样式,用以定义进度条整体
to_play_s: str
进度条样式:尚未播放的样子
played_s: str
已经播放的样子
Returns
---------
ProgressBarStyle 类
"""
self.base_style = (
base_s if base_s else r"%%N [ %%s/%^s %%% §e__________§r %%t|%^t ]"
)
self.to_play_style = to_play_s if to_play_s else r"§7="
self.played_style = played_s if played_s else r"="
@classmethod
def from_tuple(cls, tuplized_style: Optional[Tuple[str, Tuple[str, str]]]):
"""自旧版进度条元组表示法读入数据(已不建议使用)"""
if tuplized_style is None:
return cls(
r"%%N [ %%s/%^s %%% §e__________§r %%t|%^t ]",
r"§7=",
r"=",
)
if isinstance(tuplized_style, tuple):
if isinstance(tuplized_style[0], str) and isinstance(
tuplized_style[1], tuple
):
if isinstance(tuplized_style[1][0], str) and isinstance(
tuplized_style[1][1], str
):
return cls(
tuplized_style[0], tuplized_style[1][0], tuplized_style[1][1]
)
raise ValueError(
"元组表示的进度条样式组 {} 格式错误,已不建议使用此功能,请尽快更换。".format(
tuplized_style
)
)
def set_base_style(self, value: str):
"""设置基础样式"""
self.base_style = value
def set_to_play_style(self, value: str):
"""设置未播放之样式"""
self.to_play_style = value
def set_played_style(self, value: str):
"""设置已播放之样式"""
self.played_style = value
def copy(self):
dst = ProgressBarStyle(self.base_style, self.to_play_style, self.played_style)
return dst
def play_output(
self,
played_delays: int,
total_delays: int,
music_name: str = "无题",
) -> str:
"""
直接依照此格式输出一个进度条
Parameters
------------
played_delays: int
当前播放进度积分值
total_delays: int
乐器总延迟数(计分板值)
music_name: str
曲名
Returns
---------
str
进度条字符串
"""
return (
self.base_style.replace(r"%%N", music_name)
.replace(r"%%s", str(played_delays))
.replace(r"%^s", str(total_delays))
.replace(r"%%t", mctick2timestr(played_delays))
.replace(r"%^t", mctick2timestr(total_delays))
.replace(
r"%%%",
"{:0>5.2f}%".format(int(10000 * played_delays / total_delays) / 100),
)
.replace(
"_",
self.played_style,
(played_delays * self.base_style.count("_") // total_delays) + 1,
)
.replace("_", self.to_play_style)
)
def mctick2timestr(mc_tick: int) -> str:
"""
将《我的世界》的游戏刻计转为表示时间的字符串
"""
return "{:0>2d}:{:0>2d}".format(mc_tick // 1200, (mc_tick // 20) % 60)
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
r"%%N [ %%s/%^s %%% §e__________§r %%t|%^t ]",
r"§7=",
r"=",
)
"""
默认的进度条样式
"""

View File

@@ -5,8 +5,8 @@
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
@@ -16,34 +16,25 @@ Terms & Conditions: License.md in the root directory
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
import mido
from .subclass import MineNote
from .subclass import SingleNote
ProgressStyle = Tuple[str, Tuple[str, str]]
MidiNoteNameTableType = Mapping[int, Tuple[str, ...]]
"""
进度条样式类型
Midi音符名称对照表类型
"""
VoidMido = Union[mido.MidiFile, None] # void mido
MidiInstrumentTableType = Mapping[int, str]
"""
Midi类型
Midi乐器对照表类型
"""
NoteChannelType = Dict[
int,
List[SingleNote,],
]
FittingFunctionType = Callable[[float], float]
"""
频道信息类型
Dict[int,Dict[int,List[SingleNote,],],]
拟合函数类型
"""
ChannelType = Dict[
int,
Dict[
@@ -58,7 +49,25 @@ ChannelType = Dict[
],
]
"""
以字典所标记的道信息类型(已弃用)
以字典所标记的道信息类型(已弃用)
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
"""
MineNoteChannelType = Mapping[
int,
List[MineNote,],
]
"""
我的世界通道信息类型
Dict[int,Dict[int,List[MineNote,],],]
"""
MineNoteTrackType = Mapping[
int,
List[MineNote,],
]

File diff suppressed because it is too large Load Diff

68
Packer/MSCT_Packer.py Normal file
View File

@@ -0,0 +1,68 @@
import Musicreater
import Musicreater.experiment
import Musicreater.plugin
# import Musicreater.previous
from Musicreater.plugin.addonpack import (
to_addon_pack_in_delay,
to_addon_pack_in_repeater,
to_addon_pack_in_score,
)
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
from Musicreater.plugin.mcstructfile import (
to_mcstructure_file_in_delay,
to_mcstructure_file_in_repeater,
to_mcstructure_file_in_score,
)
MSCT_MAIN = (
Musicreater,
Musicreater.experiment,
# Musicreater.previous,
)
MSCT_PLUGIN = (Musicreater.plugin,)
MSCT_PLUGIN_FUNCTION = (
to_addon_pack_in_delay,
to_addon_pack_in_repeater,
to_addon_pack_in_score,
to_mcstructure_file_in_delay,
to_mcstructure_file_in_repeater,
to_mcstructure_file_in_score,
to_BDX_file_in_delay,
to_BDX_file_in_score,
)
import hashlib
import brotli
import dill
def enpack_msct_pack(sth, to_dist: str):
packing_bytes = brotli.compress(
dill.dumps(
sth,
)
)
with open(
to_dist,
"wb",
) as f:
f.write(packing_bytes)
return hashlib.sha256(packing_bytes)
with open("./Packer/checksum.txt", "w", encoding="utf-8") as f:
f.write("MSCT_MAIN:\n")
f.write(enpack_msct_pack(MSCT_MAIN, "./Packer/MSCT_MAIN.MPK").hexdigest())
f.write("\nMSCT_PLUGIN:\n")
f.write(enpack_msct_pack(MSCT_PLUGIN, "./Packer/MSCT_PLUGIN.MPK").hexdigest())
f.write("\nMSCT_PLUGIN_FUNCTION:\n")
f.write(
enpack_msct_pack(
MSCT_PLUGIN_FUNCTION, "./Packer/MSCT_PLUGIN_FUNCTION.MPK"
).hexdigest()
)

View File

@@ -1,13 +1,18 @@
<h1 align="center">
· Musicreater
</h1>
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
[Bilibili: 诸葛亮与八卦阵]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE-228B22?style=for-the-badge
<h1 align="center">· Musicreater </h1>
<p align="center">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.ico">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png">
</img>
</p>
<h3 align="center">一款免费开源的我的世界数字音频转换</h3>
<h3 align="center">一款免费开源的我的世界数字音频支持</h3>
<p align="center">
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
@@ -29,33 +34,42 @@
[![GitHub Repo stars](https://img.shields.io/github/stars/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/stargazers)
[![GitHub Repo Forks](https://img.shields.io/github/forks/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/forks)
简体中文 🇨🇳 | [English🇬🇧](README_EN.md)
简体中文🇨🇳 | [English🇬🇧](README_EN.md)
## 介绍 🚀
· 是一免费开源的针对 **我的世界** MIDI 音乐转换
· 是一免费开源的针对 **我的世界** 音乐的支持
欢迎加群[861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
> **注意** 本仓库内的项目仅仅是支持库其用户为基岩版音乐相关软件的开发者
>
> 面向常规用户的 **基岩版音乐转换工具** 请参阅[伶伦转换器](../../../Linglun-Converter)
>
> 我们也正在开发面向高级用户的 **基岩版音乐编辑工具**数字音频工作站[伶伦](../../../LinglunStudio)
## 安装 🔳
- 使用 pypi
- 使用 pypi
```bash
pip install --upgrade Musicreater
```
```bash
pip install --upgrade Musicreater
```
- 如果无法更新最新可以尝试
```bash
pip install --upgrade -i https://pypi.python.org/simple Musicreater
```
- 如果无法更新最新可以尝试
- 克隆仓库并安装**不推荐**
```bash
git clone https://gitee.com/TriM-Organization/Musicreater.git
cd Musicreater
python setup.py install
```
```bash
pip install --upgrade -i https://pypi.python.org/simple Musicreater
```
- 克隆仓库并安装最新版本但**不推荐**
```bash
git clone https://gitee.com/TriM-Organization/Musicreater.git
cd Musicreater
python setup.py install
```
以上命令中 `python``pip` 请依照各个环境不同灵活更换可能为`python3``pip3`之类
@@ -67,30 +81,32 @@
## 作者 ✒
**金羿 Eilles**我的世界基岩版指令个人开发者B 站不知名 UP 江西在校高中生
**金羿 Eilles**我的世界基岩版指令作者个人开发者B 站不知名 UP
**诸葛亮与八卦阵 bgArray**我的世界基岩版玩家喜欢编程和音乐深圳初二学生
**诸葛亮与八卦阵 bgArray**我的世界基岩版玩家喜欢编程和音乐深圳学生
**偷吃不是Touch Touch**我的世界基岩版指令提供 BDX 导入测试支持
**偷吃不是Touch Touch**我的世界基岩版指令制作者提供测试支持
## 致谢 🙏
本致谢列表排名无顺序
- 感谢 **昀梦**\<QQ1515399885\> 找出指令生成错误 bug 并指正
- 感谢由 **Charlie_Ping 查理平** 带来的 BDX 文件转换参考以及 MIDI-我的世界对应乐器 参考表格
- 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导同时我们参考了他的 BDXworkshop 作为 BDX 结构编辑的参考
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法我们将其改编并应用同时感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力让我们在原本一骑绝尘的摸鱼道路上转向开发
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题辅助我们找到了视窗操作系统下的兼容性问题感谢其反馈延迟播放器出现的重大问题让我们得以修改全部延迟播放错误尤其感谢他对于我们的软件的大力宣传
- 感谢 **Ammelia 艾米利亚**\<QQ2838334637\> 敦促我们进行新的功能开发并为新功能提出了非常优秀的大量建议以及提供的 BDX 导入测试支持为我们的新结构生成算法提供了大量的实际理论支持
- 感谢 **[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)** 对我们项目的支持与宣传非常感谢他为我们提供的服务器
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误并提醒我们修复一个一直存在的大 bug
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误并提醒修复 bug
- 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题
- <table><tr><td>感谢 **油炸**\<QQ2836146704\> 激励我们不断开发新的内容</td><td><img height="50" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg"></td></tr></table>
- 感谢 **昀梦**\<QQ1515399885\> 找出指令生成错误 bug 并指正
- 感谢由 **Charlie_Ping 查理平** 带来的 BDX 文件转换参考以及 MIDI-我的世界对应乐器 参考表格
- 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导同时我们参考了他的 BDXworkshop 作为 BDX 结构编辑的参考
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法我们将其改编并应用同时感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力让我们在原本一骑绝尘的摸鱼道路上转向开发
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题辅助我们找到了视窗操作系统下的兼容性问题感谢其反馈延迟播放器出现的重大问题让我们得以修改全部延迟播放错误尤其感谢他对于我们的软件的大力宣传
- 感谢 **Ammelia 艾米利亚**\<QQ2838334637\> 敦促我们进行新的功能开发并为新功能提出了非常优秀的大量建议以及提供的 BDX 导入测试支持为我们的新结构生成算法提供了大量的实际理论支持
- 感谢 **[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)** 对我们项目的支持与宣传非常感谢他为我们提供的服务器
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误并提醒我们修复一个一直存在的大 bug
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误并提醒修复 bug
- 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题
- <table><tr><td>感谢 **油炸**&lt;QQ2836146704&gt; 激励我们不断开发新的内容</td><td><img height="50" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg"></td></tr></table>
- 感谢 ****\<QQ237667809\> 反馈在新版本的指令格式下计分板播放器的附加包无法播放的问题
- 感谢 **梦幻duang**\<QQ13753593\> 为我们提供 Java 1.12.2 版本命令格式参考
- 感谢 [_Open Note Block Studio_](https://github.com/OpenNBS/NoteBlockStudio) 项目的开发为我们提供持续的追赶动力
> 感谢广大群友为此库提供的测试和建议等
>
> 若您对我们有所贡献但您的名字没有出现在此列表中请联系我们
## 联系 📞
@@ -109,19 +125,12 @@
此项目亦不隶属或关联于 网易
Minecraft Mojang Synergies AB 的商标此项目中所有对于我的世界Minecraft等相关称呼均为引用性使用
Minecraft Mojang Synergies AB 的商标此项目中所有对于我的世界Minecraft等相关称呼均为必要的介绍性使用
- 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易网络科技发展有限公司
- 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易璀璨网络科技有限公司
NOT AN OFFICIAL MINECRAFT PRODUCT.
NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-00A1E7?style=for-the-badge
[Bilibili: 诸葛亮与八卦阵]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge

View File

@@ -1,3 +1,10 @@
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
[Bilibili: bgArray]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE-228B22?style=for-the-badge
<h1 align="center">
· Musicreater
</h1>
@@ -7,7 +14,7 @@
</img>
</p>
<h3 align="center">A free open-source library of converting digital music files into <i>Minecraft</i> formats.</h3>
<h3 align="center">A free open-source library of <i>Minecraft</i> digital music.</h3>
<p align="center">
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
@@ -32,33 +39,40 @@
## Introduction🚀
Musicreater is a free open-source library used for converting digital music files into formats that could be read in _Minecraft_.
Musicreater is a free open-source library used for digital music that being played in _Minecraft_.
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
> **NOTICE** The project inside this repository is only the support library, which is mainly for the developers of music related software for _Minecraft: Bedrock Edition_.
>
> The _Bedrock Edition music convertor_ which is for common users is in [Linglun Converter](../../../Linglun-Converter).
>
> We are also developing a _BE music editor_ (digital audio workstation): [Linglun](../../../LinglunStudio)
## Installation 🔳
- Via pypi
- Via pypi
```bash
pip install Musicreater --upgrade
```
```bash
pip install Musicreater --upgrade
```
- If above command cannot fetch latest version, try:
```bash
pip install -i https://pypi.python.org/simple Musicreater --upgrade
```
- If above command cannot fetch latest version, try:
- Clone repo and Install (**NOT RECOMMANDED**):
```bash
git clone https://github.com/TriM-Organization/Musicreater.git
cd Musicreater
python setup.py install
```
```bash
pip install -i https://pypi.python.org/simple Musicreater --upgrade
```
- Clone repo and Install (Latest but **NOT RECOMMANDED**):
```bash
git clone https://github.com/TriM-Organization/Musicreater.git
cd Musicreater
python setup.py install
```
Commands such as `python``pip` could be changed to some like `python3` or `pip3` according to the difference of platforms.
## Documentation 📄
(Not in English yet)
@@ -69,35 +83,37 @@ Commands such as `python`、`pip` could be changed to some like `python3` or `pi
### Authors ✒
**Eilles (金羿)**A senior high school student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
**Eilles (金羿)**A student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
**bgArray (诸葛亮与八卦阵)**: A junior high school student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
**bgArray (诸葛亮与八卦阵)**: A student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
**Touch (偷吃不是Touch)**: A man who is used to use command(s) in _Minecraft: Bedrock Edition_, who supported us of debugging and testing program and algorithm
**Touch (偷吃不是 Touch)**: A man who is good at using command(s) in _Minecraft: Bedrock Edition_, who supported us of debugging and testing program and algorithm
## Thanks 🙏
## Acknowledgements 🙏
This list is not in any order.
- Thank _昀梦_\<QQ1515399885\> for finding and correcting the bugs in the commands that _Musicreater_ generated.
- Thank _Charlie_Ping 查理平_ for the bdx convert function for reference, and the reference chart that's used to convert the mid's instruments into Minecraft's instruments.
- Thank _[CMA_2401PT](https://github.com/CMA2401PT)_ for BDXWorkShop for reference of the .bdx structure's operation, and his guidance in some aspects of our development.
- Thank _[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎_ \<QQ1600515314\> for his midi analysis algorithm brought to us, we had adapted it and made it applied in one of our working method; Also, thank him for the [WebConvertor](https://dislink.github.io/midi2bdx/) which brought us so much pressure and power to develop as well as update our projects better, instead of loaf on our project.
- Thank _Mono_\<QQ738893087\> for reporting problems while installing
- Thank _Ammelia 艾米利亚_\<QQ2838334637\> for urging us to develop new functions, and put forward a lot of excellent suggestions for new functions, as well as the BDX file's importing test support provided, which has given a lot of practical theoretical support for our new Structure Generating Algorithm
- Thank _[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)_ for supporting and promoting our project, and also thanks him for his server which given us to use for free.
- Thank **指令师\_苦力怕 playjuice123**\<QQ240667197\> for finding bugs within our code, and noticed us to repair a big problem.
- Thank **雷霆**\<QQ3555268519\> for his annoying and provoking operations which may awake some problems within the program by chance and reminding us to repair.
- Thank **小埋**\<QQ2039310975\> for reporting the empty add-on packs title and description problem.
- <table><tr><td>Thank **油炸**\<QQ2836146704\> for inspiring us to constantly develop something new.</td><td><img width="260" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg" alt="The groupmate on the picture was saying that our convert-QQ-bot had once brought him great convinience but now it closed down by some reason so he was feeling regretful." title="&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;&#10;&quot;Until lose, I finally know cannot chase back what I needs&quot;"></td><td><small>&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;<br>&quot;Until lose, I finally know cannot chase back what I needs&quot;</small></td></tr></table>
- Thank _昀梦_\<QQ1515399885\> for finding and correcting the bugs in the commands that _Musicreater_ generated.
- Thank _Charlie_Ping 查理平_ for the bdx convert function for reference, and the reference chart that's used to convert the mid's instruments into Minecraft's instruments.
- Thank _[CMA_2401PT](https://github.com/CMA2401PT)_ for BDXWorkShop for reference of the .bdx structure's operation, and his guidance in some aspects of our development.
- Thank _[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎_ \<QQ1600515314\> for his midi analysis algorithm brought to us, we had adapted it and made it applied in one of our working method; Also, thank him for the [WebConvertor](https://dislink.github.io/midi2bdx/) which brought us so much pressure and power to develop as well as update our projects better, instead of loaf on our project.
- Thank _Mono_\<QQ738893087\> for reporting problems while installing
- Thank _Ammelia 艾米利亚_\<QQ2838334637\> for urging us to develop new functions, and put forward a lot of excellent suggestions for new functions, as well as the BDX file's importing test support provided, which has given a lot of practical theoretical support for our new Structure Generating Algorithm
- Thank _[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)_ for supporting and promoting our project, and also thanks him for his server which given us to use for free.
- Thank _指令师\_苦力怕 playjuice123_\<QQ240667197\> for finding bugs within our code, and noticed us to repair a big problem.
- Thank _雷霆_\<QQ3555268519\> for his annoying and provoking operations which may awake some problems within the program by chance and reminding us to repair.
- Thank _小埋_\<QQ2039310975\> for reporting the empty add-on packs title and description problem.
- <table><tr><td>Thank <i>油炸</i> &lt;QQ2836146704&gt; for inspiring us to constantly develop something new.</td><td><img width="260" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg" alt="The groupmate on the picture was saying that our convert-QQ-bot had once brought him great convinience but now it closed down by some reason so he was feeling regretful." title="&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;&#10;&quot;Until lose, I finally know cannot chase back what I needs&quot;"></td><td><small>&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;<br>&quot;Until lose, I finally know cannot chase back what I needs&quot;</small></td></tr></table>
- Thank _雨_\<QQ237667809\> for give us report that under the new `execute` command format that the scoreboard player's add-on packs cannot play correctly.
- Thank _梦幻duang_\<QQ13753593\> for providing us with his knowlodeg of the command format in Minecraft: Java Edition Version 1.12.2.
- Thank [_Open Note Block Studio_](https://github.com/OpenNBS/NoteBlockStudio)'s Project for giving us the power and energy of continual developing.
> Thanks for the support and help of a lot of groupmates
>
> Thanks for the support and help of a lot of groupmates
> If you have given contributions but have not been in the list, please contact us!
## Contact Us 📞
Meet problems? Welcome to give out your issue [here](https://github.com/EillesWan/Musicreater/issues/new)!
Meet problems? Welcome to give out your issue [here](../../issues/new)!
Want to get in contact of developers? Welcome to join our [Chat QQ group](https://jq.qq.com/?_wv=1027&k=hpeRxrYr).
@@ -117,14 +133,6 @@ NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
此项目亦不隶属或关联于 网易 相关
Minecraft Mojang Synergies AB 的商标此项目中所有对于我的世界Minecraft等相关称呼均为引用性使用
Minecraft Mojang Synergies AB 的商标此项目中所有对于我的世界Minecraft等相关称呼均为必要的介绍性使用
- 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易网络科技发展有限公司
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-00A1E7?style=for-the-badge
[Bilibili: bgArray]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge
- 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易璀璨网络科技有限公司

View File

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

View File

@@ -7,13 +7,13 @@ console = Console()
def main():
with console.status("Find the full path of .egg-info folder"):
with console.status("寻众迹于 .egg-info "):
egg_info: list = []
for file in os.listdir():
if file.endswith(".egg-info"):
egg_info.append(file)
console.print(file)
for file in track(["build", "dist", "logs", *egg_info], description="Deleting files"):
for file in track(["build", "dist", "logs", *egg_info], description="正删档"):
if os.path.isdir(file) and os.access(file, os.W_OK):
shutil.rmtree(file)

View File

@@ -1,7 +1,7 @@
<h1 align="center">· Musicreater</h1>
<p align="center">
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
</p>
**此为开发相关文档内容包括库的简单调用所生成文件结构的详细说明特殊参数的详细解释**

View File

@@ -1,7 +1,7 @@
<h1 align="center">· Musicreater</h1>
<p align="center">
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
</p>
**此为开发相关文档内容包括库的简单调用所生成文件结构的详细说明特殊参数的详细解释**
@@ -173,7 +173,7 @@
|`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://minecraft.fandom.com/zh/wiki/%E9%9F%B3%E7%AC%A6%E7%9B%92#%E4%B9%90%E5%99%A8),我们通过音调偏移来进行调整。|
|`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),我们通过音调偏移来进行调整。|
### 播放器内容
@@ -285,23 +285,31 @@
表示进度条占位的 `_` 是用来标识你的进度条的也就是可变部分的唯一的图形部分
**样式定义字符串**的样例如下这也是默认进度条的样式
**样式定义字符串基础样式**的样例如下这也是默认进度条的基础样式
` %%N [ %%s/%^s %%% __________ %%t|%^t]`
``` %%N [ %%s/%^s %%% __________ %%t|%^t]```
这是单独一行的进度条当然你也可以制作多行的如果是一行的输出时所使用的指令便是 `title`而如果是多行的话输出就会用 `titleraw` 作为进度条字幕
哦对了上面的只不过是样式定义同时还需要定义的是可变图形的部分也就是进度条上那个真正的
对于这个我们就采用了固定参数的方法对于一个进度条无非就是已经播放过的没播放过的两种形态所以使用一个元组来传入这两个参数就是最简单的了元组的格式也很简单`(str: 播放过的部分长啥样, str: 没播放过的部分长啥样)` 例如我们默认的进度的定义是这样的
对于这个我们就采用了固定参数的方法对于一个进度条无非就是已经播放过的没播放过的两种形态例如我们默认的进度**可变样式**的定义是这样的
`('§e=§r', '§7=§r')`
**可变样式甲已播放样式**`'§e=§r'`
综合起来把这些参数传给函数需要一个参数整合你猜用的啥啊对对对我用的还是元组
**可变样式乙未播放样式**`'§7=§r'`
综合起来把这些参数传给函数需要一个参数整合使用位于 `Musicreater/subclass.py` 下的 `ProgressBarStyle` 类进行定义
我们的默认定义参数如下
`(r'%%N [ %%s/%^s %%% __________ %%t|%^t]',('§e=§r', '§7=§r'))`
```python
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
r"%%N [ %%s/%^s %%% __________ %%t|%^t ]",
r"§e=§r",
r"§7=§r",
)
```
*为了避免生成错误请尽量避免使用标识符作为定义样式字符串的其他部分*

View File

@@ -1,7 +1,7 @@
<h1 align="center">· Musicreater</h1>
<p align="center">
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
</p>
# 生成文件的使用

View File

@@ -1,7 +1,7 @@
<h1 align="center">· Musicreater</h1>
<p align="center">
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
</p>
# 转换乐器对照表
@@ -12,11 +12,16 @@
**_使用时请遵循协议规定_**
- 版权所有 © 2023 · 开发者
- Copyright © 2023 all the developers of Musicreater
- 版权所有 © 2025 金羿 & 诸葛亮与八卦阵
- Copyright © 2025 Eilles & bgArray
* 开源相关声明请见 仓库根目录下的 License.md
* Terms & Conditions: License.md in the root directory
* ·本项目的协议颁发者为 金羿诸葛亮与八卦阵
* The Licensor of Musicreater("this project") is Eilles, bgArray.
本项目根据 第一版 汉钰律许可协议本协议授权
任何人皆可从以下地址获得本协议副本https://gitee.com/EillesWan/YulvLicenses
若非因法律要求或经过了特殊准许此作品在根据本协议原样提供的基础上不予提供任何形式的担保任何明示任何暗示或类似承诺也就是说用户将自行承担因此作品的质量或性能问题而产生的全部风险
详细的准许和限制条款请见原协议文本
· 开发交流群 861684859\
Email TriM-Organization@hotmail.com\

View File

@@ -0,0 +1,149 @@
# 音乐序列文件格式
· 库的音符序列文件格式包含两种一种是常规的音乐序列存储采用的 MSQ 格式另一种是为了流式读取音符而采用的 FSQ 格式
## MSQ 数据格式
MSQ 格式是 · 库存储音符序列的一种字节码格式取自 **M**usic**S**e**Q**uence 类之名
现在 · 库及其上游软件使用的是在 第二版 的基础上增设校验功能的 MSQ 第三版
### MSQ 第三版
第三版 MSQ 格式的码头是 `MSQ!` 这一版中所有的**字符串**皆以 _**GB18030**_ 编码进行编解码**数值**皆是以 _**大端字节序**_ 存储的无符号整数
码头是字节码前四个字节的内容这一部分内容是可读的 ASCII 字串因此第三版的字节码中前四个字节的内容必为 `MSQ!`
第二版 MSQ `MSQ@` 作为码头是因为美式键盘上 @ Shift+2 按下取得的故代表 MSQ 第二版
你猜为什么第三版是 `MSQ!`
#### 元信息
| 信息名称 | 西文代号 | 位长多少个 0 1 | 支持说明 |
| ------------------------------ | -------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **码头** | _无_ | 32 | 值为 `MSQ!` |
| **音乐名称长度** | music_name_length | 6 | 支持数值 0~63 |
| **最小音量** | minimum_volume | 10 | 支持数值 0~1023注意这里每个 1 代表最小音量的 0.001 个单位即取值是此处表示数字的千分倍 |
| **是否启用高精度音符时间控制** | enable_high_precision_time | 1 | 1 是启用反之同理 |
| **总音调偏移** | music_deviation | 15 | 支持数值 -16383~16383这里也是表示三位小数的和最小音量一样这里 15 位中的第一位从左往右是正负标记若为 1 则为负数反之为正数后面的 14 位是数值 |
| **音乐名称** | music_name | 依据先前定义 | 最多可支持 31 个中文字符 63 个西文字符其长度取决于先前获知的 音乐名称长度 的定义 |
在这一元信息中**音乐名称长度****最小音量**合计共 2 字节**高精度音符时间控制启用****总音调偏移**合计共 2 字节因此**音乐名称**为任意长度前四字节内容均为固定
#### 音符序列
每个序列前 4 字节为一个用以表示当前通道中音符数量的值也就是**通道音符数**notes_count也即是说一个通道内的音符可以是 0~4294967295
在这之后就是这些数量的音符了其中每个音符的信息存储方式如下
| 信息名称 | 西文代号 | 位长 | 支持说明 |
| ---------------------------- | ------------------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **乐器名称长度** | name_length | 6 | 支持数值 0~63 |
| **Midi 音高** | note_pitch | 7 | 支持数值 0~127 |
| **开始时刻** | start_tick | 17 | 单位 二十分之一秒即约为 1 命令刻支持数值 0~131071 109.22583 分钟 1.8204305 小时 |
| **音符持续刻数** | duration | 17 | 同上 |
| **是否作为打击乐器** | percussive | 1 | 1 是启用反之同理 |
| **响度力度** | velocity | 7 | 支持数值 0~127 |
| **是否启用声像位移** | is_displacement_included | 1 | 1 是启用反之同理 |
| **时间精度提升值**非必含 | high_time_precision | 8 | 支持数值 0~255若在 元信息 中启用**高精度音符时间控制**则此值启用代表音符时间控制精度偏移此值每增加 1则音符开始时刻向后增加 1/1250 |
| **乐器名称** | sound_name | 依据先前定义 | 最多可支持 31 个中文字符 63 个西文字符其长度取决于先前获知的 乐器名称长度 的定义 |
| **声像位移**非必含 | position_displacement | 共三个值每个值 16 48 | 若前述**是否启用声像位移**已启用则此值启用三个值分别代表 xyz 轴上的偏移每个值支持数值 0~65535注意这里每个 1 代表最小音量的 0.001 个单位即取值是此处表示数字的千分倍 |
#### 序列校验
_第三版新增_
在每个音符序列结尾包含一个 128 位的校验值用以标识该序列结束的同时验证该序列的完整性
在这 128 位里 64 位是该通道音符数的 XXHASH64 校验值 3 作为种子值
64 位是整个通道全部字节串的 XXHASH64 校验值包括通道开头的音符数 该通道音符数 作为种子值
#### 总体校验
_第三版新增_
在所有有效数据之后包含一个 128 位的校验值用以标识整个字节串结束的同时验证整个字节码数据的完整性
128 位的校验值是 包括码头在内的元信息的 XXHASH64 校验值种子值是全曲音符数 对于前述所有校验值彼此异或的异或 所得值之 XXHASH128 校验值 全曲音符总数 作为种子值
请注意是前述每个 XXHASH64 校验值的异或每次取 XXHASH64 都计一遍也就并非是每个序列结尾那个已经合并了的 128 位校验值再彼此异或对于这个异或值再取其种子是 全曲音符数 XXHASH128 校验字节码
听起来很复杂我来举个例子以下是该算法的伪代码我们设
- `meta_info` : `bytes` 元信息字节串
- `note_seq_1` : `bytes` 第一个音符序列的编码字节串
- `note_seq_2` : `bytes` 第二个音符序列的编码字节串
- `XXH64(bytes, seed)` : `bytes` XXHASH64 校验函数
- `XXH128(bytes, seed)` : `bytes` XXHASH128 校验函数
- `XOR(bytesLike, bytesLike)` : `bytes` 异或 函数
- `note_count` : `int` 全曲音符数
- `seq_1_note_count` : `int` 第一个音符序列的音符数
- `seq_2_note_count` : `int` 第二个音符序列的音符数
为了简化我们假设只有两个序列实际上每个通道都是一个序列最多 16 个序列
那么一个完整的 MSQ 文件应当如下排列其字节串
```assembly
ADD meta_info
ADD note_seq_1
ADD XXH64(seq_1_note_count, 3)
ADD XXH64(note_seq_1, seq_1_note_count)
ADD note_seq_2
ADD XXH64(seq_2_note_count, 3)
ADD XXH64(note_seq_2, seq_2_note_count)
ADD XXH128(
XOR(
XOR(
XXH64(meta_info, note_count),
XOR(
XXH64(seq_1_note_count, 3),
XXH64(note_seq_1, seq_1_note_count)
),
),
XOR(
XXH64(seq_2_note_count, 3),
XXH64(note_seq_2, seq_2_note_count),
)
),
note_count
)
```
## FSQ 数据格式
FSQ 格式是 · 库存储音符序列的一种字节码格式取自 **F**lowing Music **S**e**q**uence 之名
现在 · 库及其上游软件使用的是在 MSQ 第三版 的基础上进行流式的兼容性变动
### FSQ 第一版
第一版的码头是 `FSQ!` 这一版中所有的**字符串**皆以 _**GB18030**_ 编码进行编解码**数值**皆是以 _**大端字节序**_ 存储的无符号整数
码头是字节串前四个字节的内容这一部分内容是可读的 ASCII 字串因此这一版的文件前四个字节的内容必为 `FSQ!`
因为这一版本是在 MSQ 第三版的基础上演变而来的因此取自 MSQ 码头的 `MSQ!` 而改作 `FSQ!`
#### 元信息
FSQ 第一版的元信息是在 MSQ 第三版的元信息的基础上增加了一个占 5 字节的全曲音符总数
也就是说 MSQ 第三版一致的在这一组信息中**音乐名称长度****最小音量**合计共 2 字节**高精度音符时间控制启用****总音调偏移**合计共 2 字节因此**音乐名称**为任意长度前四字节内容均为固定而最后增加 5 字节作为全曲音符总数
#### 音符序列
FSQ 格式不包含音符的通道信息在读取处理时默认将同一乐器的音符视为同一通道也就是说仅存在一个序列其中每个音符的信息存储方式与 MSQ 第三版一致
音符序列的存储顺序是按照音符的**开始时间**进行排序的
但是注意有可能一个较长的音符的开始到结束时间内还包含有音符此时如有要适配的读取器还请继续读取直到下一个音符的开始时间大于此较长音符的结束时间
在每 100 个音符后插入一段 32 位的 XXHASH32 校验码其所校验的内容为这一百个音符中每个音符的第 6 个字节彼此异或之结果种子值为这一百个音符中每个音符的第 2 个字节的彼此异或结果
若最后不满足 100 个音符则不插入上述校验码
#### 总体校验
在所有有效数据之后包含一个 128 位的校验值用以标识整个字节串结束的同时验证整个字节码数据的完整性
128 位的校验值是 包括码头在内的元信息的 XXHASH64 校验值种子值是全曲音符数 对于前述所有 XXHASH32 校验值彼此异或的异或 所得值之 XXHASH128 校验值 全曲音符总数 作为种子值

View File

@@ -9,8 +9,8 @@
Musicreater (音·创)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 ./License.md
Terms & Conditions: ./License.md
@@ -19,12 +19,17 @@ Terms & Conditions: ./License.md
import os
import Musicreater
from Musicreater.plugin import ConvertConfig
from Musicreater.plugin.addonpack import (
to_addon_pack_in_delay,
to_addon_pack_in_repeater,
to_addon_pack_in_score,
)
from Musicreater.plugin.mcstructfile import (
to_mcstructure_file_in_delay,
to_mcstructure_file_in_repeater,
to_mcstructure_file_in_score,
)
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
# 获取midi列表
@@ -36,7 +41,9 @@ out_path = input(f"请输入输出路径:")
# 选择输出格式
fileFormat = int(input(f"请输入输出格式[BDX(1) 或 MCPACK(0)]").lower())
fileFormat = int(
input(f"请输入输出格式[MCSTRUCTURE(2) 或 BDX(1) 或 MCPACK(0)]").lower()
)
playerFormat = int(input(f"请选择播放方式[红石(2) 或 计分板(1) 或 延迟(0)]").lower())
@@ -50,7 +57,17 @@ def bool_str(sth: str):
elif str(sth).lower() in ("false", "", "", "f", "n"):
return False
else:
raise ValueError("布尔字符串啊?")
raise ValueError("非法逻辑字串")
def isin(sth: str, range_list: dict):
sth = sth.lower()
for bool_value, res_list in range_list.items():
if sth in res_list:
return bool_value
raise ValueError(
"不在可选范围内:{}".format([j for i in range_list.values() for j in i])
)
if os.path.exists("./demo_config.json"):
@@ -62,11 +79,11 @@ else:
# 提示语 检测函数 错误提示语
for args in [
(
f"输入音量:",
f"最小播放音量:",
float,
),
(
f"输入播放速度:",
f"播放速度:",
float,
),
(
@@ -74,59 +91,148 @@ else:
bool_str,
),
(
f"计分板名称:",
str,
)
if playerFormat == 1
else (
f"玩家选择器:",
str,
(
f"计分板名称:",
str,
)
if playerFormat == 1
else (
f"玩家选择器:",
str,
)
),
(
f"是否自动重置计分板:",
bool_str,
)
if playerFormat == 1
else (),
(
f"是否自动重置计分板:",
bool_str,
)
if playerFormat == 1
else ()
),
(
f"作者名称:",
str,
)
if fileFormat == 1
else (),
()
if playerFormat == 1
else (
f"最大结构高度:",
int,
(
f"BDX作者署名",
str,
)
if fileFormat == 1
else (
(
"结构延展方向:",
lambda a: isin(
a,
{
"z+": ["z+", "Z+"],
"x+": ["X+", "x+"],
"z-": ["Z-", "z-"],
"x-": ["x-", "X-"],
},
),
)
if (playerFormat == 2 and fileFormat == 2)
else ()
)
),
(
()
if playerFormat == 1
else (
(
"基础空白方块:",
str,
)
if (playerFormat == 2 and fileFormat == 2)
else (
f"最大结构高度:",
int,
)
)
),
]:
if args:
prompts.append(args[1](input(args[0])))
try:
prompts.append(args[1](input(args[0])))
except Exception:
print(args)
print(f"正在处理 {midi_path} ")
cvt_mid = Musicreater.MidiConvert.from_midi_file(midi_path, old_exe_format=False)
cvt_cfg = ConvertConfig(out_path, *prompts[:3])
cvt_mid = Musicreater.MidiConvert.from_midi_file(
midi_path, old_exe_format=False, min_volume=prompts[0], play_speed=prompts[1]
)
if playerFormat == 1:
cvt_method = to_addon_pack_in_score
elif playerFormat == 0:
cvt_method = to_addon_pack_in_delay
elif playerFormat == 2:
cvt_method = to_addon_pack_in_repeater
if fileFormat == 0:
if playerFormat == 1:
cvt_method = to_addon_pack_in_score
elif playerFormat == 0:
cvt_method = to_addon_pack_in_delay
elif playerFormat == 2:
cvt_method = to_addon_pack_in_repeater
elif fileFormat == 2:
if playerFormat == 1:
cvt_method = to_mcstructure_file_in_score
elif playerFormat == 0:
cvt_method = to_mcstructure_file_in_delay
elif playerFormat == 2:
cvt_method = to_mcstructure_file_in_repeater
# 测试
# print(cvt_mid)
print(
" 指令总长:{},最高延迟:{}".format(
*(cvt_method(cvt_mid, cvt_cfg, *prompts[3:])) # type: ignore
*(
cvt_method(
cvt_mid,
out_path,
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
*prompts[3:],
)
)
)
if fileFormat == 0
else " 指令总长:{},最高延迟:{},结构大小{},终点坐标{}".format(
*(
to_BDX_file_in_score(cvt_mid, cvt_cfg, *prompts[3:])
else (
" 指令总长:{},最高延迟:{},结构大小{},终点坐标{}".format(
*(
to_BDX_file_in_score(
cvt_mid,
out_path,
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
*prompts[3:],
)
if playerFormat == 1
else to_BDX_file_in_delay(
cvt_mid,
out_path,
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
*prompts[3:],
)
)
)
if fileFormat == 1
else (
" 结构大小:{},延迟总数:{},指令数量:{}".format(
*(
cvt_method(
cvt_mid,
out_path,
*prompts[3:],
)
)
)
if playerFormat == 1
else to_BDX_file_in_delay(cvt_mid, cvt_cfg, *prompts[3:])
else " 结构大小:{},延迟总数:{}".format(
*(
cvt_method(
cvt_mid,
out_path,
# Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
*prompts[3:],
)
)
)
)
)
)

View File

@@ -7,7 +7,8 @@ print(
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
input("midi路径:"), old_exe_format=False
),
Musicreater.plugin.ConvertConfig(input("输出路径:"), volume=1),
input("输出路径:"),
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
max_height=32,
)
)

View File

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

View File

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

View File

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

16
example_singleConvert.py Normal file
View File

@@ -0,0 +1,16 @@
import Musicreater
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
print(
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
Musicreater.MidiConvert.from_midi_file(
input("midi路径:"),
old_exe_format=False,
# note_table_replacement={"note.harp": "note.flute"},
),
input("输出路径:"),
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
# max_height=32,
)
)

24
example_websocket.py Normal file
View File

@@ -0,0 +1,24 @@
import Musicreater
import Musicreater.plugin
import Musicreater.plugin.websocket
import os
dire = input("midi目录")
print(
Musicreater.plugin.websocket.to_websocket_server(
[
Musicreater.MidiConvert.from_midi_file(
os.path.join(dire, names), old_exe_format=False
)
for names in os.listdir(
dire,
)
if names.endswith((".mid", ".midi"))
],
input("服务器地址:"),
int(input("服务器端口:")),
Musicreater.DEFAULT_PROGRESSBAR_STYLE,
)
)

163
let_future_java.py Normal file
View File

@@ -0,0 +1,163 @@
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
import os
import shutil
from typing import Optional, Tuple
import Musicreater.experiment
from Musicreater.plugin.archive import compress_zipfile
from Musicreater.utils import guess_deviation, is_in_diapason
def to_zip_pack_in_score(
midi_cvt: Musicreater.experiment.FutureMidiConvertJavaE,
dist_path: str,
progressbar_style: Optional[Musicreater.experiment.ProgressBarStyle],
scoreboard_name: str = "mscplay",
sound_source: str = "ambient",
auto_reset: bool = False,
) -> Tuple[int, int]:
"""
将midi以计分播放器形式转换为我的世界函数附加包
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
是否自动重置计分板
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_java_score(
scoreboard_name=scoreboard_name,
source_of_sound=sound_source,
)
# 当文件f夹{self.outputPath}/temp/mscplyfuncs存在时清空其下所有项目然后创建
if os.path.exists(f"{dist_path}/temp/mscplyfuncs/"):
shutil.rmtree(f"{dist_path}/temp/mscplyfuncs/")
os.makedirs(f"{dist_path}/temp/mscplyfuncs/mscplay")
# 写入stop.mcfunction
with open(
f"{dist_path}/temp/mscplyfuncs/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write("scoreboard players reset @a {}".format(scoreboard_name))
# 将命令列表写入文件
index_file = open(
f"{dist_path}/temp/mscplyfuncs/index.mcfunction", "w", encoding="utf-8"
)
for i in range(len(cmdlist)):
index_file.write(f"function mscplyfuncs:mscplay/track{i + 1}\n")
with open(
f"{dist_path}/temp/mscplyfuncs/mscplay/track{i + 1}.mcfunction",
"w",
encoding="utf-8",
) as f:
f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]]))
index_file.writelines(
(
"scoreboard players add @a[score_{0}_min=1] {0} 1\n".format(
scoreboard_name
),
(
"scoreboard players reset @a[score_{0}_min={1}] {0}\n".format(
scoreboard_name, maxscore + 20
)
if auto_reset
else ""
),
f"function mscplyfuncs:mscplay/progressShow\n" if progressbar_style else "",
)
)
if progressbar_style:
with open(
f"{dist_path}/temp/mscplyfuncs/mscplay/progressShow.mcfunction",
"w",
encoding="utf-8",
) as f:
f.writelines(
"\n".join(
[
single_cmd.cmd
for single_cmd in midi_cvt.form_java_progress_bar(
maxscore, scoreboard_name, progressbar_style
)
]
)
)
index_file.close()
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.zip"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.zip")
compress_zipfile(
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[JEscore].zip",
)
shutil.rmtree(f"{dist_path}/temp/")
return maxlen, maxscore
msc_cvt = Musicreater.experiment.FutureMidiConvertJavaE.from_midi_file(
input("midi路径"),
play_speed=float(input("播放速度:")),
old_exe_format=True,
note_table_replacement=Musicreater.MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
# pitched_note_table=Musicreater.MM_NBS_PITCHED_INSTRUMENT_TABLE,
)
msc_cvt.set_deviation(
guess_deviation(
msc_cvt.total_note_count,
len(msc_cvt.note_count_per_instrument),
msc_cvt.note_count_per_instrument,
music_channels=msc_cvt.channels,
)
)
in_diapason_count = 0
for this_note in [k for j in msc_cvt.channels.values() for k in j]:
if is_in_diapason(
this_note.note_pitch + msc_cvt.music_deviation, this_note.sound_name
):
in_diapason_count += 1
zip_res = to_zip_pack_in_score(
msc_cvt,
input("输出路径:"),
Musicreater.experiment.ProgressBarStyle(),
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
scoreboard_name=input("计分板名称:"),
sound_source=input("发音源:"),
auto_reset=True,
)
print(
"符合音符播放音高的音符数量:{}/{}({:.2f}%)".format(
in_diapason_count,
msc_cvt.total_note_count,
in_diapason_count * 100 / msc_cvt.total_note_count,
),
"\n指令数量:{};音乐总延迟:{}".format(*zip_res),
)

98
pyproject.toml Normal file
View File

@@ -0,0 +1,98 @@
[project]
name = "Musicreater"
dynamic = ["version"]
requires-python = ">= 3.8, < 4.0"
dependencies = [
"mido >= 1.3",
"xxhash >= 3",
]
authors = [
{ name = "金羿Eilles" },
{ name = "玉衡Alioth" },
{ name = "鱼旧梦ElapsingDreams" },
{ name = "睿乐组织 TriMO", email = "TriM-Organization@hotmail.com" },
]
maintainers = [
{ name = "金羿Eilles", email = "EillesWan@outlook.com" },
]
description = "A free open source library used for dealing with **Minecraft** digital musics."
readme = "README_EN.md"
license = { file = "LICENSE.md" }
keywords = ["midi", "minecraft", "minecraft: bedrock edition"]
classifiers = [
"Intended Audience :: Developers",
"Natural Language :: Chinese (Simplified)",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Topic :: Multimedia",
"Topic :: Multimedia :: Sound/Audio :: MIDI",
]
[project.urls]
# Homepage = "https://example.com"
# Documentation = "https://readthedocs.org"
Repository = "https://gitee.com/TriM-Organization/Musicreater"
Issues = "https://gitee.com/TriM-Organization/Musicreater/issues"
Mirror-Repository = "https://github.com/TriM-Organization/Musicreater"
Mirror-Issues = "https://github.com/TriM-Organization/Musicreater/issues"
[project.optional-dependencies]
full = [
"TrimMCStruct <= 0.0.5.9",
"brotli >= 1.0.0",
]
dev = [
"TrimMCStruct <= 0.0.5.9",
"brotli >= 1.0.0",
"dill",
"rich",
"pyinstaller",
"twine",
]
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
# https://backend.pdm-project.org/build_config/#build-configurations
[tool.pdm.build]
# includes = [
# # "README_EN.md",
# # "README.md",
# # "LICENSE.md",
# # "Musicreater/",
# # "docs/",
# ]
source-includes = [
"README_EN.md",
"README.md",
"LICENSE.md",
]
excludes = [
"fcwslib/",
"bgArrayLib/",
"Packer/",
"resources/",
"./*.mid",
"./*.msq",
"./*.fsq",
"./MSCT_Packer.py",
"resources/poem.md",
]
[tool.pdm.version]
source = "file"
path = "Musicreater/__init__.py"
[tool.pyright]
typeCheckingMode = "basic"

View File

@@ -1 +0,0 @@
mido>=1.3

View File

@@ -9,7 +9,7 @@ zh-CN 金羿
zh-TW 金羿
zh-ME 金羿羿喵
zh-HK 金 羿
en-GB Eilles Wan
en-GB Eilles
en-US EillesWan
启诸葛亮与八卦阵

View File

@@ -43,5 +43,5 @@
>
> Just let the wonderful music of peace surround the world
>
> ---- Eilles Wan
> ---- Eilles
> 7/5 2022

View File

@@ -0,0 +1,260 @@
import random
import time
from itertools import chain
from multiprocessing import Pool, Process, freeze_support
from rich.console import Console
from rich.progress import Progress
from rich.table import Table
console = Console()
# gening_stst = {"NOWIDX": 0, "DATA": {}}
# 生成单个字典的函数(用于多进程)
def generate_single_dict(args):
dict_id, dict_size = args
# if dict_id:
# console.print(
# f"字典 {dict_id + 1} 大小 {dict_size} 生成中...",
# )
# else:
# console.print(
# f"\n字典 {dict_id + 1} 大小 {dict_size} 生成中...",
# )
# final_d = {}
# gening_stst["DATA"][dict_id] = 0
# for i in range(dict_size):
# final_d[i] = [random.randint(0, 1000) for _ in range(random.randint(10000, 99999))]
# gening_stst["DATA"][dict_id] += 1
return dict_id, {
i: [random.randint(0, 1000) for _ in range(random.randint(10000, 90000))]
for i in range(dict_size)
}
# return dict_id, final_d
# 合并函数定义
def chain_merging(dict_info: dict):
return sorted(chain(*dict_info.values()))
def seq_merging(dict_info: dict):
return sorted([i for sub in dict_info.values() for i in sub])
def summing(*_):
k = []
for i in _:
k += i
return k
def plus_merging(dict_info: dict):
return sorted(summing(*dict_info.values()))
if __name__ == "__main__":
freeze_support() # Windows系统需要这个调用
# 测试配置
dict_size = 50 # 每个字典的键值对数量
num_tests = 50 # 测试次数
function_list = [chain_merging, seq_merging, plus_merging]
# dict_list = []
results = {func.__name__: [] for func in function_list}
# 多进程生成多个字典
with Progress() as progress:
task = progress.add_task("[green]进行速度测试...", total=num_tests)
# gen_task = progress.add_task("[cyan] - 生成测试数据...", total=num_tests)
with Pool() as pool:
args_list = [
(
i,
dict_size,
)
for i in range(num_tests)
]
# def disp_work():
# while gening_stst["NOWIDX"] < num_tests:
# progress.update(
# gen_task,
# advance=1,
# description=f"[cyan]正在生成 {gening_stst['DATA']['NOWIDX']}/{dict_size -1}",
# # description="正在生成..."+console._render_buffer(
# # console.render(table,),
# # ),
# )
# Process(target=disp_work).start()
for result in pool.imap_unordered(generate_single_dict, args_list):
# dict_list.append(result)
progress.update(
task,
advance=1,
description=f"[cyan]正在测试 {result[0] + 1}/{num_tests}",
# description="正在生成..."+console._render_buffer(
# console.render(table,),
# ),
# refresh=True,
)
# gening_stst["NOWIDX"] += 1
# for _ in range(num_tests):
# 随机选择字典和打乱函数顺序
# current_dict = generate_single_dict((_, dict_size))
# progress.update(
# test_task,
# advance=1,
# # description=f"[cyan]正在测试 {_}/{num_tests -1}",
# # description="正在测试..."+console._render_buffer(
# # console.render(table,progress.console.options),
# # ),
# # refresh=True,
# )
# rangen_task = progress.add_task(
# "[green]正在生成测试数据...",
# total=dict_size,
# )
# current_dict = {}
# desc = "正在生成序列 {}/{}".format("{}",dict_size-1)
# for i in range(dict_size):
# # print("正在生成第", i, "个序列",end="\r",flush=True)
# progress.update(rangen_task, advance=1, description=desc.format(i))
# current_dict[i] = [random.randint(0, 1000) for _ in range(random.randint(10000, 99999))]
shuffled_funcs = random.sample(function_list, len(function_list))
# table.rows
# table.columns = fine_column
# progress.live
# progress.console._buffer.extend(progress.console.render(table))
# for j in progress.console.render(table,progress.console.options):
# progress.console._buffer.insert(0,j)
for i, func in enumerate(shuffled_funcs):
start = time.perf_counter()
func(result[1])
elapsed = time.perf_counter() - start
results[func.__name__].append(elapsed)
# gening_stst["NOWIDX"] = num_tests
# fine_column = table.columns.copy()
# for func in function_list:
# name = func.__name__
# table.add_row(
# name,
# f"-",
# f"-",
# f"-",
# f"-",
# )
# # proc_pool = []
# 测试执行部分(保持顺序执行)
# with Progress() as progress:
# # progress.live.update(table, refresh=True)
# # progress.live.process_renderables([table],)
# # print([console._render_buffer(
# # console.render(table,),
# # )])
# # progress.console._buffer.extend(progress.console.render(table))
# test_task = progress.add_task("[cyan]进行速度测试...", total=num_tests)
# for _ in range(num_tests):
# # 随机选择字典和打乱函数顺序
# # current_dict = generate_single_dict((_, dict_size))
# progress.update(
# test_task,
# advance=1,
# description=f"[cyan]正在测试 {_}/{num_tests -1}",
# # description="正在测试..."+console._render_buffer(
# # console.render(table,progress.console.options),
# # ),
# # refresh=True,
# )
# rangen_task = progress.add_task(
# "[green]正在生成测试数据...",
# total=dict_size,
# )
# current_dict = {}
# desc = "正在生成序列 {}/{}".format("{}",dict_size-1)
# for i in range(dict_size):
# # print("正在生成第", i, "个序列",end="\r",flush=True)
# progress.update(rangen_task, advance=1, description=desc.format(i))
# current_dict[i] = [random.randint(0, 1000) for _ in range(random.randint(10000, 99999))]
# shuffled_funcs = random.sample(function_list, len(function_list))
# # table.rows
# # table.columns = fine_column
# # progress.live
# # progress.console._buffer.extend(progress.console.render(table))
# # for j in progress.console.render(table,progress.console.options):
# # progress.console._buffer.insert(0,j)
# for i, func in enumerate(shuffled_funcs):
# start = time.perf_counter()
# func(current_dict)
# elapsed = time.perf_counter() - start
# results[func.__name__].append(elapsed)
# times = results[func.__name__]
# avg_time = sum(times) / len(times)
# min_time = min(times)
# max_time = max(times)
# table.columns[0]
# table.columns[0]._cells[i] = func.__name__
# table.columns[1]._cells[i] = f"{avg_time:.5f}"
# table.columns[2]._cells[i] = f"{min_time:.5f}"
# table.columns[3]._cells[i] = f"{max_time:.5f}"
# table.columns[4]._cells[i] = str(len(times))
# progress.update(test_task, advance=0.5)
# 结果展示部分
# 结果表格
table = Table(title="\n[cyan]性能测试结果", show_header=True, header_style="bold")
table.add_column("函数名称", style="dim", width=15)
table.add_column("平均耗时 (秒)", justify="right")
table.add_column("最小耗时 (秒)", justify="right")
table.add_column("最大耗时 (秒)", justify="right")
table.add_column("测试次数", justify="right")
for i, func in enumerate(function_list):
name = func.__name__
times = results[name]
avg_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
table.add_row(
name,
f"{avg_time:.5f}",
f"{min_time:.5f}",
f"{max_time:.5f}",
str(len(times)),
)
# table.columns[0]._cells[i] = name
# table.columns[1]._cells[i] = f"{avg_time:.5f}"
# table.columns[2]._cells[i] = f"{min_time:.5f}"
# table.columns[3]._cells[i] = f"{max_time:.5f}"
# table.columns[4]._cells[i] = str(len(times))
console.print(table)

View File

@@ -0,0 +1,39 @@
import random
import time
from itertools import chain
print("生成序列中")
fine_dict = {}
for i in range(50):
print("正在生成第", i, "个序列",end="\r",flush=True)
fine_dict[i] = [random.randint(0, 1000) for _ in range(random.randint(10000, 99999))]
print("序列生成完成")
def chain_merging(dict_info: dict):
return sorted(chain(*dict_info.values()))
def seq_merging(dict_info: dict):
return sorted([i for sub in dict_info.values() for i in sub])
def summing(*_):
k = []
for i in _:
k += i
return k
def plus_merging(dict_info: dict):
return sorted(summing(*dict_info.values()))
function_list = [chain_merging, seq_merging, plus_merging]
for func in function_list:
print("正在使用",func.__name__,"函数",)
start = time.time()
func(fine_dict)
print("耗时",time.time() - start)
print("结束")

View File

@@ -1,6 +1,6 @@
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
def q_function1(x, a, a2, c1,):

View File

@@ -1,6 +1,7 @@
import matplotlib.pyplot as plt
import numpy as np
# 定义对数函数
def q_function1(vol):
# return -23.65060754864053*((x+508.2130392724084)**0.8433764630986903) + 7.257078620637543 * (x+407.86870598508153) + 1585.6201108739122

View File

@@ -11,4 +11,4 @@
不得用于商业用途
若 音·创 库被用于商业用途,应当将其剔除
版权所有 © 2023 诸葛亮与八卦阵
版权所有 © 2025 诸葛亮与八卦阵

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
import setuptools
import Musicreater
with open("requirements.txt", "r", encoding="utf-8") as fh:
dependences = fh.read().strip().split("\n")
with open("README_EN.md", "r", encoding="utf-8") as fh:
long_description = fh.read().replace(
"./docs/", "https://github.com/TriM-Organization/Musicreater/blob/master/docs/"
)
setuptools.setup(
name="Musicreater",
version=Musicreater.__version__,
author="Eilles Wan, bgArray",
author_email="TriM-Organization@hotmail.com",
description="一款免费开源的 《我的世界》 mid音乐转换库。\n"
"A free open-source python library used to convert midi into Minecraft.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/TriM-Organization/Musicreater",
packages=setuptools.find_packages(),
classifiers=[
"Intended Audience :: Developers",
"Natural Language :: Chinese (Simplified)",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
# 需要安装的依赖
install_requires=dependences,
)

39
test_fsq_opera.py Normal file
View File

@@ -0,0 +1,39 @@
from rich.pretty import pprint
import Musicreater
from Musicreater.utils import (
load_decode_fsq_flush_release,
load_decode_musicsequence_metainfo,
)
msc_seq = Musicreater.MusicSequence.from_mido(
Musicreater.mido.MidiFile(
"./resources/测试片段.mid",
),
"TEST-测试片段",
)
pprint("音乐源取入成功:")
pprint(msc_seq)
with open("test.fsq", "wb") as f:
f.write(fsq_bytes := msc_seq.encode_dump(flowing_codec_support=True))
with open("test.fsq", "rb") as f:
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read(), verify=True)
pprint("FSQ 传入类成功:")
pprint(msc_seq_r)
with open("test.fsq", "rb") as f:
pprint("流式 FSQ 元数据:")
pprint(metas := load_decode_musicsequence_metainfo(f))
pprint("流式 FSQ 音符序列:")
cnt = 0
for i in load_decode_fsq_flush_release(f, metas[-2], metas[-3], metas[-1]):
pprint(
i,
)
cnt += 1
pprint(f"{cnt} 个音符")

33
test_future_kamires.py Normal file
View File

@@ -0,0 +1,33 @@
import Musicreater.experiment
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
msct = Musicreater.experiment.FutureMidiConvertKamiRES.from_midi_file(
input("midi路径:"), old_exe_format=False
)
opt = input("输出路径:")
print(
"乐器使用情况",
)
for name in sorted(
set(
[
n.split(".")[0].replace("c", "").replace("d", "")
for n in msct.note_count_per_instrument.keys()
]
)
):
print("\t", name, flush=True)
print(
"\n输出:",
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
msct,
opt,
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
max_height=32,
),
)

33
test_future_lyric.py Normal file
View File

@@ -0,0 +1,33 @@
import Musicreater.experiment
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
msct = Musicreater.experiment.FutureMidiConvertLyricSupport.from_midi_file(
input("midi路径:"), old_exe_format=False
)
opt = input("输出路径:")
# print(
# "乐器使用情况",
# )
# for name in sorted(
# set(
# [
# n.split(".")[0].replace("c", "").replace("d", "")
# for n in msct.note_count_per_instrument.keys()
# ]
# )
# ):
# print("\t", name, flush=True)
print(
"\n输出:",
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
msct,
opt,
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
max_height=32,
),
)

34
test_msq_opera.py Normal file
View File

@@ -0,0 +1,34 @@
from rich.pretty import pprint
import Musicreater
from Musicreater.utils import (
load_decode_msq_flush_release,
load_decode_musicsequence_metainfo,
)
msc_seq = Musicreater.MusicSequence.from_mido(
Musicreater.mido.MidiFile(
"./resources/测试片段.mid",
),
"TEST-测试片段",
)
pprint("音乐源取入成功:")
pprint(msc_seq)
with open("test.msq", "wb") as f:
f.write(msq_bytes := msc_seq.encode_dump())
with open("test.msq", "rb") as f:
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read())
pprint("常规 MSQ 读取成功:")
pprint(msc_seq_r)
with open("test.msq", "rb") as f:
pprint("流式 MSQ 元数据:")
pprint(metas := load_decode_musicsequence_metainfo(f))
pprint("流式 MSQ 音符序列:")
for i in load_decode_msq_flush_release(f, metas[-2], metas[-3], metas[-1]):
pprint(i)

View File

@@ -1,3 +1,7 @@
python setup.py sdist bdist_wheel
python -m twine upload dist/*
uv build
python -m twine check dist/*
pause
uv publish
pause
python clean_update.py
pause

1253
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff