mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-27 19:55:42 +00:00
Compare commits
64 Commits
v1.7.3
...
v3.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4901cf3dc | ||
|
|
13512df9ce | ||
|
|
1d9931f79d | ||
|
|
841f6e53c6 | ||
|
|
0de959c396 | ||
|
|
734ee2dd66 | ||
|
|
32b7930b26 | ||
|
|
583ca04ac9 | ||
|
|
1e0698c85d | ||
|
|
dc3be4120d | ||
|
|
bf173a9f81 | ||
|
|
6bea46977f | ||
|
|
3b8532af61 | ||
|
|
9f35e9e16a | ||
|
|
65358b12d0 | ||
|
|
046c2af27c | ||
|
|
fa6cfb4873 | ||
|
|
3d6d0e17a2 | ||
|
|
f1bea1d57e | ||
|
|
63b59442a4 | ||
|
|
7f74b29569 | ||
|
|
885dc085ea | ||
|
|
24742a140c | ||
|
|
6f94f477f2 | ||
|
|
c056eb1fcb | ||
|
|
b796a363d8 | ||
|
|
774f78359c | ||
|
|
8df63df26c | ||
|
|
9764019f08 | ||
|
|
250857c78f | ||
|
|
7ab6b77cff | ||
|
|
321f80540c | ||
|
|
50373b05d0 | ||
|
|
b65483b32d | ||
|
|
45e9adbbd1 | ||
|
|
7a1ca86132 | ||
|
|
64048a5e31 | ||
|
|
3739138059 | ||
|
|
048b631bd6 | ||
|
|
23bf69619b | ||
|
|
b4e16353ec | ||
|
|
c14489f3a7 | ||
|
|
889f8f9641 | ||
|
|
2df8d6a270 | ||
|
|
78eabf2df6 | ||
|
|
b3ddfc7ade | ||
|
|
bd031ef547 | ||
|
|
e4304dc3d1 | ||
|
|
794ee6d080 | ||
|
|
b67554c7c9 | ||
|
|
6f391567ba | ||
|
|
932b9a34e0 | ||
|
|
f6b884e38f | ||
|
|
bf1b7b99d8 | ||
|
|
fe7c11636b | ||
|
|
382b0e1601 | ||
|
|
c17ff0e53a | ||
|
|
f1ab2373b5 | ||
|
|
77351d767b | ||
|
|
69624df711 | ||
|
|
d4925e4d75 | ||
|
|
95c0ff1b47 | ||
|
|
3831c41b9a | ||
|
|
a4565b8529 |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -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
|
||||
@@ -21,6 +24,9 @@ RES.txt
|
||||
/MSCT_Packer.py
|
||||
/Packer/*.MPK
|
||||
/Packer/checksum.txt
|
||||
/bgArrayLib
|
||||
/fcwslib
|
||||
test_lyric-mido.py
|
||||
|
||||
# Byte-compiled / optimized
|
||||
__pycache__/
|
||||
@@ -46,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
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.10
|
||||
266
LICENSE.md
266
LICENSE.md
@@ -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. 若用户二次分发此作品,可以选择向此作品的接收者提供无偿或有偿的担保维修、支持服务或其他责任、义务。但是,该用户只可以其自己的名义提供上述内容,不得以任何其他贡献者的名义。且该用户必须明确表明任何此类责任或义务是由其个人独立提供,且其同意并应当承担赔偿此作品的全体贡献者因其个人承担上述责任义务而产生的任何赔偿责任。
|
||||
|
||||
4. 用户不得删除或更改此作品中包含的任何许可声明(包括版权声明,专利声明,免责声明,或赔偿责任限制),除非该更改是对已知事实错误的修补、或其已获得此作品颁发者的特殊许可、或更改者即为此作品颁发者本人。
|
||||
|
||||
5. 若此作品将权益的声明通知作为一部分,那么由用户分发的任何版本的作品中须至少在下列三处之一包含该声明通知的自然人可读副本:
|
||||
|
||||
- 该作品的权益声明通知中
|
||||
- 在源形式的文件中(当且仅当该作品开放源代码)
|
||||
- 在惯例中作为第三方通知出现之处(当且仅当该作品会产生画面,且该画面可被自然人详细观察)
|
||||
|
||||
该通知的内容仅供信息提供,不应对许可证进行任何文字上的修改。用户可在其分发的作品中,在不构成修改本协议的前提下,在作品自身的声明通知或属性描述后或作为附录添加。
|
||||
|
||||
6. 依据本款第3条,若用户二次分发此作品时,选择向作品的接收者提供收费的担保服务,则必须明确告知该接收者本协议全部内容与此作品原出处,并确保其知悉上述内容;但若用户在二次分发此作品时,不选择提供任何服务,则该用户不允许向作品的接收者收取任何费用,除非该用户获得了此作品颁发者的特殊许可、或该用户即为此作品颁发者本人。
|
||||
|
||||
## 五、提交贡献
|
||||
|
||||
除非贡献者明确声明,在本作品中由该贡献者向颁发者的提供的提交,必须符合本协议的条款,并与本协议的条款不存在冲突;除非此贡献中与本协议冲突的附加条款已获得颁发者的特殊许可、或贡献者即为此作品颁发者本人。
|
||||
|
||||
## 六、商标相关
|
||||
|
||||
本协议并未授予用户,将颁发者的商标、专属标记或特定产品名称,用于合理的或惯例性的描述或此类声明之外其他任何位置的权利。
|
||||
|
||||
## 七、免责声明
|
||||
|
||||
1. 若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,**不予提供任何形式的担保、任何明示、任何暗示或类似承诺**,此类包括但不限于担保此作品毫无缺陷、担保此作品适于贩卖、担保此作品适于特定目的、担保使用此作品绝不侵权。用户将自行承担因此作品的质量或性能问题而产生的全部风险。若此作品在任何方面欠妥,将由用户(而非任何贡献者、而非任何颁发者)承担所有必要的服务、维修或除错的任何成本。本免责声明是本许可的重要组成部分。当且仅当遵守本免责声明时,本协议的其他条款中对本作品的使用授权方可生效。
|
||||
|
||||
2. 无论是因何种原因,如果不是在法律规定的特殊情况(如,确为贡献者的故意或重大过失)下或者经过了特殊准许,即使贡献者事先已知发生损害的可能,在使用本作品时,用户产生的任何直接、间接、特殊、偶然或必然造成的损失(包括但不限于商誉损失、工作延误、计算机系统故障等),**均不由任一贡献者承担**。
|
||||
|
||||
**以上是本许可协议的全部条款**
|
||||
|
||||
---
|
||||
|
||||
附录
|
||||
|
||||
**如何在自己的作品中应用 汉钰律许可协议**
|
||||
|
||||
若要在自己源形式的作品应用本协议,请在其中附加下面的通知模板,并将六角括号“〔〕”中的字段替换成自身的实际信息来替换(不包括括号本身)。这些文本必须以对应文件格式适当的注释句法包含在其中,可以是实体的纸质文档、也可以是网络公告或者计算机文件;或者脱离该源之外,另起一个新的文件,使之指向要应用本协议的那个作品。同时也建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的页面上作为版权通知的整体,这样更加容易的区分出第三方内容。
|
||||
|
||||
若需要在自己以目标形式存在的作品中应用本协议,同样需要附加下面的通知模板并更改六角括号中的字样。但是,这些文本可以是位于作品的标签上、位于作品的用户可见且能被自然人详细观察的画面之中、或者按照惯例中许可协议应该出现的位置;同时,这些文本的所处位置应当能够明确指示到本协议应用的那个作品。另外,建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的位置上作为版权通知的整体,这样更加容易的区分出第三方内容。
|
||||
|
||||
**通知模板**
|
||||
|
||||
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.
|
||||
```
|
||||
版权所有 © 〔年份〕 〔著作权人〕
|
||||
〔或者:版权所有 (C) 〔年份〕 〔著作权人〕〕
|
||||
|
||||
〔该作品〕根据 汉钰律许可协议,第一版(“本协议”)授权。
|
||||
任何人皆可从以下地址获得本协议副本:〔本协议副本所在地址〕。
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||
详细的准许和限制条款请见原协议文本。
|
||||
```
|
||||
@@ -1,15 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""一个简单的我的世界音频转换库
|
||||
音·创 (Musicreater)
|
||||
是一款免费开源的针对《我的世界》的midi音乐转换库
|
||||
Musicreater(音·创)
|
||||
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
|
||||
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
"""
|
||||
音·创
|
||||
是一款免费开源的《我的世界》数字音频支持库。
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
Musicreater (音·创)
|
||||
A free and open-source library for handling with **Minecraft** digital music.
|
||||
|
||||
版权所有 © 2026 睿乐组织
|
||||
Copyright © 2026 TriM-Organization
|
||||
|
||||
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
|
||||
The Licensor of Musicreater("this project") is Eilles, YuhengAlioth
|
||||
|
||||
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||
任何人皆可从以下地址获得本协议副本:
|
||||
https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,
|
||||
不予提供任何形式的担保、任何明示、任何暗示或类似承诺。
|
||||
也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||
详细的准许和限制条款请见原协议文本。
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
@@ -17,29 +27,45 @@ Terms & Conditions: License.md in the root directory
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__version__ = "1.7.3"
|
||||
__vername__ = "功能结构化"
|
||||
__author__ = (
|
||||
("金羿", "Eilles Wan"),
|
||||
("诸葛亮与八卦阵", "bgArray"),
|
||||
("偷吃不是Touch", "Touch"),
|
||||
("鸣凤鸽子", "MingFengPigeon"),
|
||||
)
|
||||
__all__ = [
|
||||
# 主要类
|
||||
"MidiConvert",
|
||||
# 附加类
|
||||
"SingleNote",
|
||||
"SingleCommand",
|
||||
"SingleNoteBox",
|
||||
# "TimeStamp", 未来功能
|
||||
# 默认值
|
||||
"DEFAULT_PROGRESSBAR_STYLE",
|
||||
"MM_INSTRUMENT_DEVIATION_TABLE",
|
||||
"MM_CLASSIC_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE",
|
||||
"MM_TOUCH_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE",
|
||||
]
|
||||
__version__ = "3.0.0-alpha"
|
||||
|
||||
from .main import *
|
||||
__author__ = (
|
||||
("金羿", "Eilles"),
|
||||
("玉衡Alioth", "YuhengAlioth"),
|
||||
("鱼旧梦", "ElapsingDreams"),
|
||||
("偷吃不是Touch", "Touch"),
|
||||
)
|
||||
|
||||
from .paramcurve import ParamCurve, InterpolationMethod, BoundaryBehaviour
|
||||
|
||||
from .data import (
|
||||
SingleMusic,
|
||||
SingleTrack,
|
||||
SingleNote,
|
||||
SoundAtmos,
|
||||
MineNote,
|
||||
CurvableParam,
|
||||
)
|
||||
|
||||
from .plugins import load_plugin_module
|
||||
|
||||
from .main import MusiCreater
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"__author__",
|
||||
# 参数曲线相关
|
||||
"ParamCurve",
|
||||
"InterpolationMethod",
|
||||
"BoundaryBehaviour",
|
||||
# 音乐数据结构
|
||||
"SingleMusic",
|
||||
"SingleTrack",
|
||||
"SingleNote",
|
||||
"SoundAtmos",
|
||||
"MineNote",
|
||||
"CurvableParam",
|
||||
# 工程项目相关
|
||||
"load_plugin_module",
|
||||
"MusiCreater",
|
||||
]
|
||||
|
||||
632
Musicreater/_plugin_abc.py
Normal file
632
Musicreater/_plugin_abc.py
Normal file
@@ -0,0 +1,632 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储 音·创 v3 的插件基类,提供抽象接口以供实际插件使用
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2026 金羿
|
||||
Copyright © 2026 Eilles
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
# =====================
|
||||
# NOTE: [WARNING]
|
||||
# 这个文件是一坨屎山代码
|
||||
# 请勿模仿,请多包容
|
||||
# =====================
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Dict,
|
||||
Any,
|
||||
Optional,
|
||||
List,
|
||||
Tuple,
|
||||
Union,
|
||||
Sequence,
|
||||
BinaryIO,
|
||||
Generator,
|
||||
Iterator,
|
||||
Set,
|
||||
)
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
import tomli_w
|
||||
else:
|
||||
import tomli as tomllib # 第三方包
|
||||
import tomli_w
|
||||
|
||||
from .exceptions import (
|
||||
PluginConfigDumpError,
|
||||
PluginConfigLoadError,
|
||||
PluginMetainfoNotFoundError,
|
||||
PluginMetainfoTypeError,
|
||||
PluginMetainfoValueError,
|
||||
PluginAttributeNotFoundError,
|
||||
ParameterTypeError,
|
||||
PluginInstanceNotFoundError,
|
||||
)
|
||||
from .data import SingleMusic, SingleTrack
|
||||
|
||||
# 已经全部由 plugins.py 提供接口
|
||||
# 请用户从 plugins.py 导入
|
||||
# 不要在这里导,会坏掉的
|
||||
|
||||
# __all__ = [
|
||||
# # 枚举类
|
||||
# "PluginType",
|
||||
# # 抽象基类/数据类(插件参数定义)
|
||||
# "PluginConfig",
|
||||
# "PluginMetaInformation",
|
||||
# # 抽象基类(插件定义)
|
||||
# "MusicInputPlugin",
|
||||
# "TrackInputPlugin",
|
||||
# "MusicOperatePlugin",
|
||||
# "TrackOperatePlugin",
|
||||
# "MusicOutputPlugin",
|
||||
# "TrackOutputPlugin",
|
||||
# "ServicePlugin",
|
||||
# "LibraryPlugin",
|
||||
# # 插件注册用装饰函数
|
||||
# "music_input_plugin",
|
||||
# "track_input_plugin",
|
||||
# "music_operate_plugin",
|
||||
# "track_operate_plugin",
|
||||
# "music_output_plugin",
|
||||
# "track_output_plugin",
|
||||
# "service_plugin",
|
||||
# "library_plugin",
|
||||
# ]
|
||||
|
||||
|
||||
# ========================
|
||||
# 枚举类
|
||||
# ========================
|
||||
|
||||
class PluginTypes(str, Enum):
|
||||
"""插件类型枚举"""
|
||||
|
||||
FUNCTION_MUSIC_IMPORT = "import_music_data"
|
||||
FUNCTION_TRACK_IMPORT = "import_track_data"
|
||||
FUNCTION_MUSIC_OPERATE = "music_data_operating"
|
||||
FUNCTION_TRACK_OPERATE = "track_data_operating"
|
||||
FUNCTION_MUSIC_EXPORT = "export_music_data"
|
||||
FUNCTION_TRACK_EXPORT = "export_track_data"
|
||||
SERVICE = "service"
|
||||
LIBRARY = "library"
|
||||
|
||||
|
||||
|
||||
# ========================
|
||||
# 数据类
|
||||
# ========================
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginConfig(ABC):
|
||||
"""插件配置基类"""
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""将配置内容转换为字典
|
||||
|
||||
返回
|
||||
====
|
||||
Dict[str, Any]
|
||||
配置项的字典表示,不包含以下划线开头的私有属性
|
||||
"""
|
||||
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "PluginConfig":
|
||||
"""从字典创建配置实例
|
||||
|
||||
参数
|
||||
====
|
||||
data: Dict[str, Any]
|
||||
包含配置字段的字典
|
||||
|
||||
返回
|
||||
====
|
||||
PluginConfig
|
||||
配置类的实例
|
||||
"""
|
||||
|
||||
# 只保留类中定义的字段
|
||||
field_names = {f.name for f in cls.__dataclass_fields__.values()}
|
||||
filtered_data = {k: v for k, v in data.items() if k in field_names}
|
||||
return cls(**filtered_data)
|
||||
|
||||
def save_to_file(self, file_path: Path) -> None:
|
||||
"""保存配置到 TOML 文件
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
目标文件路径;必须以 .toml 为后缀
|
||||
|
||||
异常
|
||||
====
|
||||
PluginConfigDumpError
|
||||
当文件后缀不是 .toml 或写入失败时抛出
|
||||
"""
|
||||
if file_path.suffix.upper() == ".TOML":
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
raise PluginConfigDumpError(
|
||||
"插件配置文件类型不应为`{}`,须为`TOML`格式。".format(file_path.suffix)
|
||||
)
|
||||
|
||||
try:
|
||||
with file_path.open("wb") as f:
|
||||
tomli_w.dump(self.to_dict(), f, multiline_strings=False, indent=4)
|
||||
except Exception as e:
|
||||
raise PluginConfigDumpError(e)
|
||||
|
||||
@classmethod
|
||||
def load_from_file(cls, file_path: Path) -> "PluginConfig":
|
||||
"""从 TOML 文件加载配置
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
源文件路径
|
||||
|
||||
返回
|
||||
====
|
||||
PluginConfig
|
||||
加载后的配置实例
|
||||
|
||||
异常
|
||||
====
|
||||
PluginConfigLoadError
|
||||
当读取或解析失败时抛出
|
||||
"""
|
||||
try:
|
||||
with file_path.open("rb") as f:
|
||||
return cls.from_dict(tomllib.load(f))
|
||||
except Exception as e:
|
||||
raise PluginConfigLoadError(e)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginMetaInformation(ABC):
|
||||
"""插件元信息"""
|
||||
|
||||
name: str
|
||||
"""插件名称,应为惟一之名"""
|
||||
author: str
|
||||
"""插件作者"""
|
||||
description: str
|
||||
"""插件简介"""
|
||||
version: Tuple[int, ...]
|
||||
"""插件版本号"""
|
||||
type: PluginTypes
|
||||
"""插件类型"""
|
||||
license: str = "MIT License"
|
||||
"""插件发布时采用的许可协议"""
|
||||
dependencies: Sequence[str] = tuple()
|
||||
"""插件是否对其他插件存在依赖"""
|
||||
|
||||
|
||||
# ========================
|
||||
# 抽象基类
|
||||
# ========================
|
||||
|
||||
|
||||
class TopPluginBase(ABC):
|
||||
"""所有插件的抽象基类"""
|
||||
|
||||
metainfo: PluginMetaInformation
|
||||
"""插件元信息"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
if hasattr(cls, "metainfo"):
|
||||
if not isinstance(cls.metainfo, PluginMetaInformation):
|
||||
raise PluginMetainfoTypeError(
|
||||
"类`{cls_name}`之属性`metainfo`的类型,必须为`PluginMetaInformation`".format(
|
||||
cls_name=cls.__name__
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not cls.__name__.endswith("PluginBase"):
|
||||
raise PluginMetainfoNotFoundError(
|
||||
"类`{cls_name}`必须定义一个`metainfo`属性。".format(
|
||||
cls_name=cls.__name__
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TopInOutPluginBase(TopPluginBase, ABC):
|
||||
"""导入导出用抽象基类"""
|
||||
|
||||
supported_formats: Tuple[str, ...] = tuple()
|
||||
"""支持的格式(定义后会自动转大写)"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if hasattr(cls, "supported_formats"):
|
||||
if cls.supported_formats:
|
||||
# 强制转换为大写,并使用元组
|
||||
cls.supported_formats = tuple(map(str.upper, cls.supported_formats))
|
||||
else:
|
||||
cls.supported_formats = tuple()
|
||||
else:
|
||||
raise PluginAttributeNotFoundError(
|
||||
"用于导入导出数据的类`{cls_name}`必须定义一个`supported_formats`属性。".format(
|
||||
cls_name=cls.__name__
|
||||
)
|
||||
)
|
||||
|
||||
def can_handle_file(self, file_path: Path) -> bool:
|
||||
"""判断是否可处理某个文件
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
待检测的文件路径
|
||||
|
||||
返回
|
||||
====
|
||||
bool
|
||||
若文件后缀已在本类中定义,则返回 True
|
||||
"""
|
||||
return file_path.suffix.upper().endswith(self.supported_formats)
|
||||
|
||||
def can_handle_format(self, format_name: str) -> bool:
|
||||
"""判断是否可处理某个格式
|
||||
|
||||
参数
|
||||
====
|
||||
format_name: str
|
||||
格式名称(如 'MIDI', 'WAV')
|
||||
|
||||
返回
|
||||
====
|
||||
bool
|
||||
若格式名本类中已经定义,则返回 True
|
||||
"""
|
||||
return format_name.upper().endswith(self.supported_formats)
|
||||
|
||||
|
||||
class MusicInputPluginBase(TopInOutPluginBase, ABC):
|
||||
"""导入用插件抽象基类-完整曲目"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_IMPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||
) -> "SingleMusic":
|
||||
"""从字节流加载数据到完整曲目
|
||||
|
||||
参数
|
||||
====
|
||||
bytes_buffer_in: BinaryIO
|
||||
输入的二进制字节流
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleMusic
|
||||
解析得到的完整曲目对象
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
|
||||
"""从文件加载数据到完整曲目
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
输入文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleMusic
|
||||
解析得到的完整曲目对象
|
||||
"""
|
||||
with file_path.open("rb") as f:
|
||||
return self.loadbytes(f, config)
|
||||
|
||||
|
||||
class TrackInputPluginBase(TopInOutPluginBase, ABC):
|
||||
"""导入用插件抽象基类-单个音轨"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_IMPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||
) -> "SingleTrack":
|
||||
"""从字节流加载音符数据到单个音轨
|
||||
|
||||
参数
|
||||
====
|
||||
bytes_buffer_in: BinaryIO
|
||||
输入的二进制字节流
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleTrack
|
||||
解析得到的单个音轨对象
|
||||
"""
|
||||
pass
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack":
|
||||
"""从文件加载音符数据到单个音轨
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
输入文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleTrack
|
||||
解析得到的单个音轨对象
|
||||
"""
|
||||
with file_path.open("rb") as f:
|
||||
return self.loadbytes(f, config)
|
||||
|
||||
|
||||
class MusicOperatePluginBase(TopPluginBase, ABC):
|
||||
"""音乐处理用插件抽象基类-完整曲目"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_OPERATE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def process(
|
||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||
) -> "SingleMusic":
|
||||
"""处理完整曲目的数据
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleMusic
|
||||
待处理的完整曲目
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleMusic
|
||||
处理后的完整曲目
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TrackOperatePluginBase(TopPluginBase, ABC):
|
||||
"""音乐处理用插件抽象基类-单个音轨"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_OPERATE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def process(
|
||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||
) -> "SingleTrack":
|
||||
"""处理单个音轨的音符数据
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleTrack
|
||||
待处理的单个音轨
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleTrack
|
||||
处理后的单个音轨
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
||||
"""导出用插件的抽象基类-完整曲目"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_EXPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def dumpbytes(
|
||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将完整曲目导出为对应格式的字节流
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleMusic
|
||||
待导出的完整曲目
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
BinaryIO
|
||||
导出后的二进制字节流
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将完整曲目导出为对应格式的文件
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleMusic
|
||||
待导出的完整曲目
|
||||
file_path: Path
|
||||
输出文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
||||
"""导出用插件的抽象基类-单个音轨"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_EXPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def dumpbytes(
|
||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将单个音轨导出为对应格式的字节流
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleTrack
|
||||
待导出的单个音轨
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
BinaryIO
|
||||
导出后的二进制字节流
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将单个音轨导出为对应格式的文件
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleTrack
|
||||
待导出的单个音轨
|
||||
file_path: Path
|
||||
输出文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ServicePluginBase(TopPluginBase, ABC):
|
||||
"""服务插件抽象基类"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginTypes.SERVICE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`ServicePlugin`继承的,该类的子类应当为一个`PluginType.SERVICE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def serve(self, config: Optional[PluginConfig], *args) -> None:
|
||||
"""服务插件的运行逻辑
|
||||
|
||||
参数
|
||||
====
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
*args: Any
|
||||
其他运行时参数
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class LibraryPluginBase(TopPluginBase, ABC):
|
||||
"""插件依赖库的抽象基类"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginTypes.LIBRARY:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
# 怎么?
|
||||
# 插件的彼此依赖就不需要什么调用了吧
|
||||
59
Musicreater/builtin_plugins/midi_read.py
Normal file
59
Musicreater/builtin_plugins/midi_read.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
音·创 v3 内置的 Midi 读取插件
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2026 金羿、玉衡Alioth
|
||||
Copyright © 2026 Eilles, YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
import mido
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from typing import BinaryIO, Optional
|
||||
|
||||
from Musicreater import SingleMusic
|
||||
from Musicreater.plugins import (
|
||||
music_input_plugin,
|
||||
PluginConfig,
|
||||
PluginMetaInformation,
|
||||
PluginTypes,
|
||||
MusicInputPluginBase,
|
||||
)
|
||||
|
||||
|
||||
@music_input_plugin("midi_2_music_plugin")
|
||||
class MidiImport2MusicPlugin(MusicInputPluginBase):
|
||||
"""Midi 音乐数据导入插件"""
|
||||
|
||||
metainfo = PluginMetaInformation(
|
||||
name="Midi 导入插件",
|
||||
author="金羿、玉衡Alioth",
|
||||
description="从 Midi 文件导入音乐数据",
|
||||
version=(0, 0, 1),
|
||||
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
||||
license="Same as Musicreater",
|
||||
)
|
||||
|
||||
supported_formats = ("MID", "MIDI")
|
||||
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: PluginConfig | None
|
||||
) -> SingleMusic:
|
||||
midi_file = mido.MidiFile(file=bytes_buffer_in)
|
||||
return SingleMusic() # =========================== TODO: 等待制作
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
|
||||
"""从 Midi 文件导入音乐数据"""
|
||||
midi_file = mido.MidiFile(filename=file_path)
|
||||
return SingleMusic() # =========================== TODO: 等待制作
|
||||
File diff suppressed because it is too large
Load Diff
786
Musicreater/data.py
Normal file
786
Musicreater/data.py
Normal file
@@ -0,0 +1,786 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储 音·创 v3 的内部数据类
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2026 金羿
|
||||
Copyright © 2026 Eilles
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
# “
|
||||
# 把代码 洒落在这里
|
||||
# 和音符 留下的沙砾
|
||||
# 一点一点爬进你类定义的缝隙
|
||||
# ” —— 乐曲访问 by resnah
|
||||
|
||||
import heapq
|
||||
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf, ceil
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
Optional,
|
||||
Any,
|
||||
List,
|
||||
Tuple,
|
||||
Union,
|
||||
Dict,
|
||||
Set,
|
||||
Sequence,
|
||||
Callable,
|
||||
Generator,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Literal,
|
||||
Hashable,
|
||||
TypeVar,
|
||||
)
|
||||
from enum import Enum
|
||||
|
||||
from .exceptions import SingleNoteDecodeError, ParameterTypeError, ParameterValueError
|
||||
from .paramcurve import ParamCurve
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class SoundAtmos:
|
||||
"""声源方位类"""
|
||||
|
||||
sound_distance: float
|
||||
"""声源距离 方块"""
|
||||
|
||||
sound_azimuth: Tuple[float, float]
|
||||
"""声源方位 角度(rV左右 rH上下)"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
distance: Optional[float] = None,
|
||||
azimuth: Optional[Tuple[float, float]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
定义一个发声方位
|
||||
|
||||
Parameters
|
||||
------------
|
||||
distance: float
|
||||
发声源距离玩家的距离(半径 `r`)
|
||||
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
|
||||
azimuth: tuple[float, float]
|
||||
声源方位
|
||||
注:此参数为tuple,包含两个元素,分别表示:
|
||||
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
||||
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
||||
"""
|
||||
|
||||
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]:
|
||||
"""声像位移,直接可应用于我的世界的相对视角的坐标参考系中(^x ^y ^z)"""
|
||||
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_time: int
|
||||
"""开始之时 命令刻"""
|
||||
|
||||
duration: int
|
||||
"""音符持续时间 命令刻"""
|
||||
|
||||
high_precision_start_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
|
||||
是否作为打击乐器
|
||||
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_time: int = start_time
|
||||
"""开始之时 命令刻"""
|
||||
self.duration: int = last_time
|
||||
"""音符持续时间 命令刻"""
|
||||
self.high_precision_start_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 Exception as e:
|
||||
# 我也不知道为什么这里要放一个异常处理
|
||||
# 之前用到过吗?
|
||||
# —— 2026.01.25 金羿
|
||||
print(
|
||||
"[Exception] 单音符解析错误,字节码`{}`,{}启用高精度时间偏移\n".format(
|
||||
code_buffer, "已" if is_high_time_precision else "未"
|
||||
)
|
||||
)
|
||||
raise SingleNoteDecodeError(
|
||||
e,
|
||||
"技术信息:\nGROUP1\t`{}`\nCODE_BUFFER\t`{}`".format(
|
||||
group_1, code_buffer
|
||||
),
|
||||
)
|
||||
|
||||
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_time)
|
||||
<< 17
|
||||
)
|
||||
+ self.duration
|
||||
).to_bytes(6, "big")
|
||||
# + self.track_no.to_bytes(1, "big")
|
||||
+ (
|
||||
self.high_precision_start_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 ParameterTypeError(
|
||||
"参数类型错误;键:`{}` 值:`{}`".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_time,
|
||||
self.duration,
|
||||
self.high_precision_start_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_time,
|
||||
self.duration,
|
||||
self.high_precision_start_time,
|
||||
)
|
||||
|
||||
def __dict__(self):
|
||||
return {
|
||||
"Pitch": self.note_pitch,
|
||||
"Velocity": self.velocity,
|
||||
"StartTick": self.start_time,
|
||||
"Duration": self.duration,
|
||||
"TimeOffset": self.high_precision_start_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_time == other.start_tick:
|
||||
return self.high_precision_start_time < other.high_precision_time
|
||||
else:
|
||||
return self.start_time < other.start_tick
|
||||
|
||||
def __gt__(self, other) -> bool:
|
||||
"""比较自己是否在开始时间上晚于另一个音符"""
|
||||
if self.start_time == other.start_tick:
|
||||
return self.high_precision_start_time > other.high_precision_time
|
||||
else:
|
||||
return self.start_time > other.start_tick
|
||||
|
||||
|
||||
class CurvableParam(str, Enum):
|
||||
"""可曲线化的参数枚举类"""
|
||||
|
||||
PITCH = "adjust_note_pitch"
|
||||
VELOCITY = "adjust_note_velocity"
|
||||
VOLUME = "adjust_note_volume"
|
||||
DISTANCE = "adjust_note_sound_distance"
|
||||
LR_PANNING = "adjust_note_leftright_panning_degree"
|
||||
UD_PANNING = "adjust_note_updown_panning_degree"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MineNote:
|
||||
"""我的世界音符对象(仅提供我的世界相关接口)"""
|
||||
|
||||
pitch: float
|
||||
"""midi音高"""
|
||||
instrument: str
|
||||
"""乐器ID"""
|
||||
velocity: float
|
||||
"""力度"""
|
||||
volume: float
|
||||
"""音量"""
|
||||
start_tick: int
|
||||
"""开始之时 命令刻"""
|
||||
duration_tick: int
|
||||
"""音符持续时间 命令刻"""
|
||||
persiced_time: int
|
||||
"""高精度开始时间偏量 1/1250 秒"""
|
||||
percussive: bool
|
||||
"""是否作为打击乐器启用"""
|
||||
position: SoundAtmos
|
||||
"""声像方位"""
|
||||
|
||||
@classmethod
|
||||
def from_single_note(
|
||||
cls,
|
||||
note: SingleNote,
|
||||
note_instrument: str,
|
||||
sound_volume: float,
|
||||
is_persiced_time: bool,
|
||||
is_percussive_note: bool,
|
||||
sound_position: SoundAtmos,
|
||||
adjust_note_pitch: float = 0.0,
|
||||
adjust_note_velocity: float = 0.0,
|
||||
adjust_note_volume: float = 0.0,
|
||||
adjust_note_sound_distance: float = 0.0,
|
||||
adjust_note_leftright_panning_degree: float = 0.0,
|
||||
adjust_note_updown_panning_degree: float = 0.0,
|
||||
) -> "MineNote":
|
||||
"""从SingleNote对象创建MineNote对象"""
|
||||
sound_position.sound_distance += adjust_note_sound_distance
|
||||
sound_position.sound_azimuth = (
|
||||
sound_position.sound_azimuth[0] + adjust_note_leftright_panning_degree,
|
||||
sound_position.sound_azimuth[1] + adjust_note_updown_panning_degree,
|
||||
)
|
||||
return cls(
|
||||
pitch=note.note_pitch + adjust_note_pitch,
|
||||
instrument=note_instrument,
|
||||
velocity=note.velocity + adjust_note_velocity,
|
||||
volume=sound_volume + adjust_note_volume,
|
||||
start_tick=note.start_time,
|
||||
duration_tick=note.duration,
|
||||
persiced_time=note.high_precision_start_time if is_persiced_time else 0,
|
||||
percussive=is_percussive_note,
|
||||
position=sound_position,
|
||||
)
|
||||
|
||||
|
||||
class SingleTrack(List[SingleNote]):
|
||||
"""存储单个轨道的类"""
|
||||
|
||||
track_name: str
|
||||
"""轨道之名称"""
|
||||
|
||||
is_enabled: bool = True
|
||||
"""该音轨是否启用"""
|
||||
|
||||
track_instrument: str
|
||||
"""乐器ID"""
|
||||
|
||||
track_volume: float
|
||||
"""该音轨的音量"""
|
||||
|
||||
is_high_time_precision: bool
|
||||
"""该音轨是否使用高精度时间"""
|
||||
|
||||
is_percussive: bool
|
||||
"""该音轨是否标记为打击乐器轨道"""
|
||||
|
||||
sound_position: SoundAtmos
|
||||
"""声像方位"""
|
||||
|
||||
argument_curves: Dict[CurvableParam, Union[ParamCurve, Literal[None]]]
|
||||
"""参数曲线"""
|
||||
|
||||
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 {}
|
||||
|
||||
self.argument_curves = {item: None for item in CurvableParam}
|
||||
|
||||
super().__init__(*args)
|
||||
super().sort()
|
||||
|
||||
def disable(self) -> None:
|
||||
"""禁用音轨"""
|
||||
|
||||
self.is_enabled = False
|
||||
|
||||
def enable(self) -> None:
|
||||
"""启用音轨"""
|
||||
|
||||
self.is_enabled = True
|
||||
|
||||
def toggle_able(self) -> None:
|
||||
"""切换音轨的启用状态"""
|
||||
|
||||
self.is_enabled = not self.is_enabled
|
||||
|
||||
def append(self, object: SingleNote) -> None:
|
||||
"""
|
||||
添加一个音符,推荐使用 add 方法
|
||||
"""
|
||||
|
||||
return self.add(object)
|
||||
|
||||
def add(self, item: SingleNote) -> None:
|
||||
"""
|
||||
在音轨里添加一个音符
|
||||
"""
|
||||
|
||||
if not isinstance(item, SingleNote):
|
||||
raise ParameterTypeError(
|
||||
"单音轨类的元素类型须为单音符(`SingleNote`),不可为:`{}`".format(
|
||||
type(item).__name__
|
||||
)
|
||||
)
|
||||
super().append(item)
|
||||
super().sort() # =========================== TODO 需要优化
|
||||
|
||||
def update(self, items: Iterable[SingleNote]):
|
||||
"""
|
||||
拼接两个音轨
|
||||
"""
|
||||
super().extend(items)
|
||||
super().sort() # =========================== TODO 需要优化
|
||||
|
||||
def get(self, time: int) -> Generator[SingleNote, None, None]:
|
||||
"""通过开始时间来获取音符"""
|
||||
|
||||
return (x for x in self if x.start_time == time)
|
||||
|
||||
def get_notes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Generator[SingleNote, None, None]:
|
||||
"""通过开始时间和结束时间来获取音符"""
|
||||
if end_time < start_time:
|
||||
raise ParameterValueError(
|
||||
"获取音符的时间范围有误,终止时间`{}`早于起始时间`{}`".format(
|
||||
end_time, start_time
|
||||
)
|
||||
)
|
||||
elif start_time < 0 or end_time < 0:
|
||||
raise ParameterValueError(
|
||||
"获取音符的时间范围有误,终止时间`{}`和起始时间`{}`皆不可为负数".format(
|
||||
end_time, start_time
|
||||
)
|
||||
)
|
||||
return (
|
||||
x
|
||||
for x in self
|
||||
if (x.start_time >= start_time) and (x.start_time <= end_time)
|
||||
)
|
||||
|
||||
def get_minenotes(
|
||||
self, range_start_time: float = 0, range_end_time: float = inf
|
||||
) -> Generator[MineNote, Any, None]:
|
||||
"""获取能够用以在我的世界播放的音符数据类"""
|
||||
|
||||
for _note in self.get_notes(range_start_time, range_end_time):
|
||||
yield MineNote.from_single_note(
|
||||
note=_note,
|
||||
note_instrument=self.track_instrument,
|
||||
sound_volume=self.track_volume,
|
||||
is_persiced_time=self.is_high_time_precision,
|
||||
is_percussive_note=self.is_percussive,
|
||||
sound_position=self.sound_position,
|
||||
**{
|
||||
item.value: argcrv.value_at(_note.start_time)
|
||||
for item in CurvableParam
|
||||
if (argcrv := self.argument_curves[item])
|
||||
},
|
||||
)
|
||||
|
||||
@property
|
||||
def note_amount(self) -> int:
|
||||
"""音符数"""
|
||||
return len(self)
|
||||
|
||||
@property
|
||||
def track_notes(self) -> List[SingleNote]:
|
||||
"""音符列表"""
|
||||
return 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 ParameterTypeError(
|
||||
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||
)
|
||||
|
||||
def get_info(self, key: str, default: Any = None) -> Any:
|
||||
"""获取附加信息"""
|
||||
return self.extra_info.get(key, default)
|
||||
|
||||
|
||||
class SingleMusic(List[SingleTrack]):
|
||||
"""存储单个曲子的类"""
|
||||
|
||||
music_name: str
|
||||
"""乐曲名称"""
|
||||
|
||||
music_creator: str
|
||||
"""本我的世界曲目的制作者"""
|
||||
|
||||
music_original_author: str
|
||||
"""曲目的原作者"""
|
||||
|
||||
music_description: str
|
||||
"""当前曲目的简介"""
|
||||
|
||||
music_credits: str
|
||||
"""曲目的版权信息"""
|
||||
|
||||
# 感叹一下什么交冗余设计啊!(叉腰)
|
||||
extra_info: Dict[str, Any]
|
||||
"""这还得放东西?"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "未命名乐曲",
|
||||
creator: str = "未命名制作者",
|
||||
original_author: str = "未命名原作者",
|
||||
description: str = "未命名简介",
|
||||
credits: str = "未命名版权信息",
|
||||
*args: SingleTrack,
|
||||
extra_information: Dict[str, Any] = {},
|
||||
):
|
||||
self.music_name = name
|
||||
"""乐曲名称"""
|
||||
|
||||
self.music_creator = creator
|
||||
"""曲目制作者"""
|
||||
|
||||
self.music_original_author = original_author
|
||||
"""乐曲原作者"""
|
||||
|
||||
self.music_description = description
|
||||
"""简介"""
|
||||
|
||||
self.music_credits = credits
|
||||
"""版权信息"""
|
||||
|
||||
self.extra_info = extra_information if extra_information else {}
|
||||
|
||||
super().__init__(*args)
|
||||
|
||||
@property
|
||||
def track_amount(self) -> int:
|
||||
"""音轨数"""
|
||||
return len(self)
|
||||
|
||||
@property
|
||||
def music_tracks(self) -> Iterator[SingleTrack]:
|
||||
"""音轨列表,不包含被禁用的音轨"""
|
||||
return (track for track in self if track.is_enabled)
|
||||
|
||||
@staticmethod
|
||||
def yield_from_tracks(
|
||||
tracks: Sequence[Iterator[T]],
|
||||
sort_key: Callable[[T], Any],
|
||||
is_subseq_sorted: bool = True,
|
||||
) -> Iterator[T]:
|
||||
"""从任意迭代器列表迭代符合顺序的元素
|
||||
(惰性多路归并多个迭代器,按 sort_key 排序)
|
||||
|
||||
参数
|
||||
----
|
||||
tracks: Sequence[Iterator[T]]
|
||||
迭代器列表
|
||||
sort_key: Callable[[T], Any]
|
||||
接受 T 元素,返回可比较的键
|
||||
is_subseq_sorted: bool = True
|
||||
子序列是否已排序
|
||||
|
||||
迭代
|
||||
----
|
||||
归并后的每个元素,按 sort_key 升序
|
||||
"""
|
||||
if is_subseq_sorted:
|
||||
return heapq.merge(*tracks, key=sort_key)
|
||||
else:
|
||||
# 初始化堆
|
||||
heap_pool: List[Tuple[Any, int, T]] = []
|
||||
for _index, _track in enumerate(tracks):
|
||||
try:
|
||||
item = next(_track)
|
||||
heapq.heappush(heap_pool, (sort_key(item), _index, item))
|
||||
except StopIteration:
|
||||
continue
|
||||
|
||||
# 归并主循环
|
||||
while heap_pool:
|
||||
_key, _index, item = heapq.heappop(heap_pool)
|
||||
yield item
|
||||
try:
|
||||
next_item = next(tracks[_index])
|
||||
heapq.heappush(heap_pool, (sort_key(next_item), _index, next_item))
|
||||
except StopIteration:
|
||||
pass
|
||||
# NEVER REACH:
|
||||
# pool: List[Tuple[str, T]] = []
|
||||
# remove_track: List[str] = []
|
||||
# for _name, _track in tracks.items():
|
||||
# try:
|
||||
# pool.append((_name, next(_track)))
|
||||
# except StopIteration:
|
||||
# remove_track.append(_name)
|
||||
# for _x in remove_track:
|
||||
# tracks.pop(_x)
|
||||
# del remove_track
|
||||
# while tracks and pool:
|
||||
# yield (_x := min(pool, key=sort_key))[1]
|
||||
# try:
|
||||
# pool.append((_x[0], next(tracks[_x[0]])))
|
||||
# except StopIteration:
|
||||
# tracks.pop(_x[0])
|
||||
# pool.sort(key=sort_key)
|
||||
# for _remain in pool:
|
||||
# yield _remain[1]
|
||||
|
||||
def get_tracked_notes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Generator[Iterator[SingleNote], Any, None]:
|
||||
"""获取指定时间段的各个音轨的音符数据"""
|
||||
return (track.get_notes(start_time, end_time) for track in self.music_tracks)
|
||||
|
||||
def get_tracked_minenotes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Generator[Iterator[MineNote], Any, None]:
|
||||
"""获取指定时间段的各个音轨的,供我的世界播放的音符数据类"""
|
||||
return (
|
||||
track.get_minenotes(start_time, end_time) for track in self.music_tracks
|
||||
)
|
||||
|
||||
def get_notes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Iterator[SingleNote]:
|
||||
"""获取指定时间段的所有音符数据,按照时间顺序"""
|
||||
if self.track_amount == 0:
|
||||
return iter(())
|
||||
return self.yield_from_tracks(
|
||||
[track.get_notes(start_time, end_time) for track in self.music_tracks],
|
||||
sort_key=lambda x: x.start_time,
|
||||
)
|
||||
|
||||
def get_minenotes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Generator[MineNote, Any, None]:
|
||||
"""获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序"""
|
||||
if self.track_amount == 0:
|
||||
return
|
||||
yield from self.yield_from_tracks(
|
||||
[track.get_minenotes(start_time, end_time) for track in self.music_tracks],
|
||||
sort_key=lambda x: x.start_tick,
|
||||
)
|
||||
|
||||
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 ParameterTypeError(
|
||||
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||
)
|
||||
|
||||
def get_info(self, key: str, default: Any = None) -> Any:
|
||||
"""获取附加信息"""
|
||||
return self.extra_info.get(key, default)
|
||||
@@ -1,12 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存放一堆报错类型
|
||||
存储 音·创 v3 用到的一些报错类型
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||
Copyright © 2026 Eilles & YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -16,102 +16,251 @@ Terms & Conditions: License.md in the root directory
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
# “
|
||||
# There are planty of "exception"s in this library
|
||||
# for I know I will always go with my heart.
|
||||
# ” —— Cyberdevil by resnah
|
||||
|
||||
class MSCTBaseException(Exception):
|
||||
"""音·创库版本的所有错误均继承于此"""
|
||||
|
||||
class MusicreaterBaseException(Exception):
|
||||
"""音·创 v3 的所有错误均继承于此"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音·创库版本的所有错误均继承于此"""
|
||||
super().__init__(*args)
|
||||
"""音·创 的所有错误均继承于此"""
|
||||
super().__init__("[音·创] - ", *args)
|
||||
|
||||
def meow(
|
||||
self,
|
||||
):
|
||||
def meow(self):
|
||||
for i in self.args:
|
||||
print(i + "喵!")
|
||||
print(i + "喵~", end=":")
|
||||
|
||||
def crash_it(self):
|
||||
raise self
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "".join(self.args)
|
||||
|
||||
class MidiFormatException(MSCTBaseException):
|
||||
"""音·创库版本的所有MIDI格式错误均继承于此"""
|
||||
|
||||
# =====================================
|
||||
# NOTE
|
||||
# 面对用户时候爆出去的我们认为这就是“外部错误”
|
||||
# 如果是在程序内部数据传输等情况下出现的就是“内部错误”
|
||||
# 例如,无法读取文件,这就是一个外部错误
|
||||
# 某个参数的数据类型错误,这就是内部错误
|
||||
# =====================================
|
||||
|
||||
|
||||
class MusicreaterInnerlyError(MusicreaterBaseException):
|
||||
"""内部错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音·创库版本的所有MIDI格式错误均继承于此"""
|
||||
super().__init__("MIDI格式错误", *args)
|
||||
"""内部错误(面向开发者的报错信息)"""
|
||||
super().__init__("内部错误 - ", *args)
|
||||
|
||||
|
||||
class MidiDestroyedError(MSCTBaseException):
|
||||
"""Midi文件损坏"""
|
||||
class MusicreaterOuterlyError(MusicreaterBaseException):
|
||||
"""外部错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Midi文件损坏"""
|
||||
super().__init__("MIDI文件损坏:无法读取MIDI文件", *args)
|
||||
"""外部错误(面向用户的报错信息)"""
|
||||
super().__init__("外部错误 - ", *args)
|
||||
|
||||
|
||||
class MidiUnboundError(MSCTBaseException):
|
||||
"""未定义Midi对象"""
|
||||
class InnerlyParameterError(MusicreaterInnerlyError):
|
||||
"""内部传参错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""未绑定Midi对象"""
|
||||
super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args)
|
||||
"""参数错误"""
|
||||
super().__init__("传参错误 - ", *args)
|
||||
|
||||
|
||||
class CommandFormatError(RuntimeError):
|
||||
"""指令格式与目标格式不匹配而引起的错误"""
|
||||
class ParameterTypeError(InnerlyParameterError, TypeError):
|
||||
"""参数类型错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""指令格式与目标格式不匹配而引起的错误"""
|
||||
super().__init__("指令格式不匹配", *args)
|
||||
"""参数类型错误"""
|
||||
super().__init__("参数类型错误:", *args)
|
||||
|
||||
|
||||
# class CrossNoteError(MidiFormatException):
|
||||
# """同通道下同音符交叉出现所产生的错误"""
|
||||
|
||||
# def __init__(self, *args):
|
||||
# """同通道下同音符交叉出现所产生的错误"""
|
||||
# super().__init__("同通道下同音符交叉", *args)
|
||||
# 这TM是什么错误?
|
||||
# 我什么时候写的这玩意?
|
||||
# 我哪知道这说的是啥?
|
||||
|
||||
|
||||
class NotDefineTempoError(MidiFormatException):
|
||||
"""没有Tempo设定导致时间无法计算的错误"""
|
||||
class ParameterValueError(InnerlyParameterError, ValueError):
|
||||
"""参数值存在错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""没有Tempo设定导致时间无法计算的错误"""
|
||||
super().__init__("在曲目开始时没有声明Tempo(未指定拍长)", *args)
|
||||
"""参数其值存在错误"""
|
||||
super().__init__("参数数值错误:", *args)
|
||||
|
||||
|
||||
class ChannelOverFlowError(MidiFormatException):
|
||||
"""一个midi中含有过多的通道"""
|
||||
|
||||
def __init__(self, max_channel=16, *args):
|
||||
"""一个midi中含有过多的通道"""
|
||||
super().__init__("含有过多的通道(数量应≤{})".format(max_channel), *args)
|
||||
|
||||
|
||||
class NotDefineProgramError(MidiFormatException):
|
||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||
class PluginNotSpecifiedError(InnerlyParameterError, LookupError):
|
||||
"""未指定插件"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||
super().__init__("未指定演奏乐器", *args)
|
||||
"""未指定插件"""
|
||||
super().__init__("未指定插件:", *args)
|
||||
|
||||
|
||||
class NoteOnOffMismatchError(MidiFormatException):
|
||||
"""音符开音和停止不匹配的错误"""
|
||||
class OuterlyParameterError(MusicreaterOuterlyError):
|
||||
"""外部参数错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音符开音和停止不匹配的错误"""
|
||||
super().__init__("音符不匹配", *args)
|
||||
"""参数错误"""
|
||||
super().__init__("参数错误 - ", *args)
|
||||
|
||||
|
||||
class ZeroSpeedError(ZeroDivisionError):
|
||||
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
|
||||
"""以0作为播放速度的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""以0作为播放速度的错误"""
|
||||
super().__init__("播放速度为0", *args)
|
||||
super().__init__("播放速度为零:", *args)
|
||||
|
||||
|
||||
class IllegalMinimumVolumeError(OuterlyParameterError, ValueError):
|
||||
"""最小播放音量有误的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""最小播放音量错误"""
|
||||
super().__init__("最小播放音量超出范围:", *args)
|
||||
|
||||
|
||||
class FileFormatNotSupportedError(MusicreaterOuterlyError):
|
||||
"""不支持的文件格式"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""文件格式不受支持"""
|
||||
super().__init__("不支持的文件格式:", *args)
|
||||
|
||||
|
||||
class NoteBinaryDecodeError(MusicreaterOuterlyError):
|
||||
"""音乐存储二进制数据解码错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音乐存储二进制数据无法正确解码"""
|
||||
super().__init__("解码音乐存储二进制数据时出现问题 - ", *args)
|
||||
|
||||
|
||||
class SingleNoteDecodeError(NoteBinaryDecodeError):
|
||||
"""单个音符的二进制数据解码错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""单个音符的二进制数据无法正确解码"""
|
||||
super().__init__("音符解码出错:", *args)
|
||||
|
||||
|
||||
class NoteBinaryFileTypeError(NoteBinaryDecodeError):
|
||||
"""音乐存储二进制数据的文件类型错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""无法识别音乐存储文件的类型"""
|
||||
super().__init__("无法识别音乐存储文件对应的类型:", *args)
|
||||
|
||||
|
||||
class NoteBinaryFileVerificationFailed(NoteBinaryDecodeError):
|
||||
"""音乐存储二进制数据校验失败"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音乐存储文件与其校验值不一致"""
|
||||
super().__init__("音乐存储文件校验失败:", *args)
|
||||
|
||||
|
||||
class PluginDefineError(MusicreaterInnerlyError):
|
||||
"""插件定义错误(内部相关)"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件本身存在错误"""
|
||||
super().__init__("插件内部错误 - ", *args)
|
||||
|
||||
|
||||
class PluginInstanceNotFoundError(PluginDefineError, LookupError):
|
||||
"""插件实例未找到"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件实例未找到"""
|
||||
super().__init__("插件实例未找到:", *args)
|
||||
|
||||
|
||||
class PluginAttributeNotFoundError(PluginDefineError, AttributeError):
|
||||
"""插件属性定义错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件属性定义错误"""
|
||||
super().__init__("插件类的必要属性不存在:", *args)
|
||||
|
||||
|
||||
class PluginMetainfoError(PluginDefineError):
|
||||
"""插件元信息定义错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件元信息定义错误"""
|
||||
super().__init__("插件元信息定义错误 - ", *args)
|
||||
|
||||
|
||||
class PluginMetainfoTypeError(PluginMetainfoError, TypeError):
|
||||
"""插件元信息定义类型错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件元信息定义类型错误"""
|
||||
super().__init__("插件元信息类型错误:", *args)
|
||||
|
||||
|
||||
class PluginMetainfoValueError(PluginMetainfoError, ValueError):
|
||||
"""插件元信息定义值错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件元信息定义值错误"""
|
||||
super().__init__("插件元信息数值错误:", *args)
|
||||
|
||||
|
||||
class PluginMetainfoNotFoundError(PluginMetainfoError, PluginAttributeNotFoundError):
|
||||
"""插件元信息定义缺少错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件元信息定义缺少错误"""
|
||||
super().__init__("插件元信息未定义:", *args)
|
||||
|
||||
|
||||
class PluginLoadError(MusicreaterOuterlyError):
|
||||
"""插件加载错误(外部相关)"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件加载错误"""
|
||||
super().__init__("插件加载错误 - ", *args)
|
||||
|
||||
|
||||
class PluginNotFoundError(PluginLoadError):
|
||||
"""插件未找到"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件未找到"""
|
||||
super().__init__("插件未找到:", *args)
|
||||
|
||||
|
||||
class PluginRegisteredError(PluginLoadError):
|
||||
"""插件重复注册"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件已被注册注册"""
|
||||
super().__init__("插件重复注册:", *args)
|
||||
|
||||
|
||||
|
||||
class PluginConfigRelatedError(MusicreaterOuterlyError):
|
||||
"""插件配置相关错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件配置相关错误"""
|
||||
super().__init__("插件配置相关错误 - ", *args)
|
||||
|
||||
|
||||
class PluginConfigLoadError(PluginLoadError, PluginConfigRelatedError):
|
||||
"""插件配置加载错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""配置文件无法加载"""
|
||||
super().__init__("插件配置文件加载错误:", *args)
|
||||
|
||||
|
||||
class PluginConfigDumpError(PluginConfigRelatedError):
|
||||
"""插件配置保存错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""配置文件无法保存"""
|
||||
super().__init__("插件配置文件保存错误:", *args)
|
||||
|
||||
@@ -1,405 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
新版本功能以及即将启用的函数
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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 .subclass import *
|
||||
from .utils import *
|
||||
from .main import (
|
||||
MidiConvert,
|
||||
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
|
||||
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
|
||||
)
|
||||
from .types import Tuple, List, Dict, ChannelType
|
||||
|
||||
|
||||
class FutureMidiConvertRSNB(MidiConvert):
|
||||
"""
|
||||
加入红石音乐适配
|
||||
"""
|
||||
|
||||
music_command_list: Dict[int, SingleNoteBox]
|
||||
"""音乐指令列表"""
|
||||
|
||||
|
||||
class FutureMidiConvertM4(MidiConvert):
|
||||
"""
|
||||
加入插值算法优化音感
|
||||
: 经测试,生成效果已经达到,感觉良好
|
||||
"""
|
||||
|
||||
# 临时用的插值计算函数
|
||||
@staticmethod
|
||||
def _linear_note(
|
||||
_note: SingleNote,
|
||||
_apply_time_division: float = 100,
|
||||
) -> List[SingleNote]:
|
||||
"""传入音符数据,返回以半秒为分割的插值列表
|
||||
:param _note: SingleNote 音符
|
||||
:param _apply_time_division: int 间隔毫秒数
|
||||
:return list[tuple(int开始时间(毫秒), int乐器, int音符, int力度(内置), float音量(播放)),]"""
|
||||
|
||||
if _note.percussive:
|
||||
return [
|
||||
_note,
|
||||
]
|
||||
|
||||
totalCount = int(_note.duration / _apply_time_division)
|
||||
|
||||
if totalCount == 0:
|
||||
return [
|
||||
_note,
|
||||
]
|
||||
# print(totalCount)
|
||||
|
||||
result: List[SingleNote] = []
|
||||
|
||||
for _i in range(totalCount):
|
||||
result.append(
|
||||
SingleNote(
|
||||
instrument=_note.inst,
|
||||
pitch=_note.pitch,
|
||||
velocity=_note.velocity,
|
||||
startime=int(_note.start_time + _i * (_note.duration / totalCount)),
|
||||
lastime=int(_note.duration / totalCount),
|
||||
track_number=_note.track_no,
|
||||
is_percussion=_note.percussive,
|
||||
extra_information=_note.extra_info,
|
||||
)
|
||||
# (
|
||||
# _note.start_time + _i * _apply_time_division,
|
||||
# _note.instrument,
|
||||
# _note.pitch,
|
||||
# _note.velocity,
|
||||
# ((totalCount - _i) / totalCount),
|
||||
# )
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def to_command_list_in_delay(
|
||||
self,
|
||||
max_volume: float = 1.0,
|
||||
speed: float = 1.0,
|
||||
player_selector: str = "@a",
|
||||
) -> Tuple[List[SingleCommand], int, int]:
|
||||
"""
|
||||
将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_volume: float
|
||||
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
||||
speed: float
|
||||
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||
player_selector: str
|
||||
玩家选择器,默认为`@a`
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple( list[SingleCommand,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
||||
"""
|
||||
|
||||
if speed == 0:
|
||||
raise ZeroSpeedError("播放速度仅可为(0,1]范围内的正实数")
|
||||
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
||||
|
||||
notes_list: List[SingleNote] = []
|
||||
|
||||
# 此处 我们把通道视为音轨
|
||||
for channel in self.to_music_note_channels().values():
|
||||
for note in channel:
|
||||
note.set_info(
|
||||
note_to_command_parameters(
|
||||
note,
|
||||
(
|
||||
self.percussion_note_referrence_table
|
||||
if note.percussive
|
||||
else self.pitched_note_reference_table
|
||||
),
|
||||
(max_volume) if note.track_no == 0 else (max_volume * 0.9),
|
||||
self.volume_processing_function,
|
||||
)
|
||||
)
|
||||
|
||||
if not note.percussive:
|
||||
notes_list.extend(self._linear_note(note, note.extra_info[3] * 500))
|
||||
else:
|
||||
notes_list.append(note)
|
||||
|
||||
notes_list.sort(key=lambda a: a.start_time)
|
||||
|
||||
self.music_command_list = []
|
||||
multi = max_multi = 0
|
||||
delaytime_previous = 0
|
||||
|
||||
for note in notes_list:
|
||||
delaytime_now = round(note.start_time / speed / 50)
|
||||
if (tickdelay := (delaytime_now - delaytime_previous)) == 0:
|
||||
multi += 1
|
||||
else:
|
||||
max_multi = max(max_multi, multi)
|
||||
multi = 0
|
||||
self.music_command_list.append(
|
||||
SingleCommand(
|
||||
self.execute_cmd_head.format(player_selector)
|
||||
+ r"playsound {} @s ^ ^ ^{} {} {}".format(*note.extra_info),
|
||||
tick_delay=tickdelay,
|
||||
annotation="在{}播放{}%的{}音".format(
|
||||
mctick2timestr(delaytime_now),
|
||||
max_volume * 100,
|
||||
"{}:{}".format(note.extra_info[0], note.extra_info[3]),
|
||||
),
|
||||
)
|
||||
)
|
||||
delaytime_previous = delaytime_now
|
||||
|
||||
self.music_tick_num = round(notes_list[-1].start_time / speed / 50)
|
||||
return self.music_command_list, self.music_tick_num, max_multi + 1
|
||||
|
||||
|
||||
class FutureMidiConvertM5(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 = 500000
|
||||
|
||||
# 我们来用通道统计音乐信息
|
||||
# 但是是用分轨的思路的
|
||||
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: ChannelType = 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_in_delay(
|
||||
self,
|
||||
max_volume: float = 1.0,
|
||||
speed: float = 1.0,
|
||||
player_selector: str = "@a",
|
||||
) -> Tuple[List[SingleCommand], int]:
|
||||
"""
|
||||
使用金羿的转换思路,使用同刻偏移算法优化音感后,将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_volume: float
|
||||
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
|
||||
speed: float
|
||||
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||
player_selector: str
|
||||
玩家选择器,默认为`@a`
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple( list[SingleCommand,...], int音乐时长游戏刻 )
|
||||
"""
|
||||
|
||||
if speed == 0:
|
||||
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
|
||||
|
||||
self.to_music_channels()
|
||||
|
||||
tracks = {}
|
||||
InstID = -1
|
||||
|
||||
# 此处 我们把通道视为音轨
|
||||
for i in self.channels.keys():
|
||||
# 如果当前通道为空 则跳过
|
||||
if not self.channels[i]:
|
||||
continue
|
||||
|
||||
# 第十通道是打击乐通道
|
||||
SpecialBits = True if i == 9 else False
|
||||
|
||||
# nowChannel = []
|
||||
|
||||
for track_no, track in self.channels[i].items():
|
||||
for msg in track:
|
||||
if msg[0] == "PgmC":
|
||||
InstID = msg[1]
|
||||
|
||||
elif msg[0] == "NoteS":
|
||||
soundID, _X = (
|
||||
inst_to_sould_with_deviation(
|
||||
msg[1], MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
if SpecialBits
|
||||
else inst_to_sould_with_deviation(
|
||||
InstID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
)
|
||||
|
||||
score_now = round(msg[-1] / float(speed) / 50)
|
||||
# print(score_now)
|
||||
|
||||
try:
|
||||
tracks[score_now].append(
|
||||
self.execute_cmd_head.format(player_selector)
|
||||
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
|
||||
+ (
|
||||
""
|
||||
if SpecialBits
|
||||
else f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
tracks[score_now] = [
|
||||
self.execute_cmd_head.format(player_selector)
|
||||
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
|
||||
+ (
|
||||
""
|
||||
if SpecialBits
|
||||
else f"{2 ** ((msg[1] - 60 - _X) / 12)}"
|
||||
)
|
||||
]
|
||||
|
||||
all_ticks = list(tracks.keys())
|
||||
all_ticks.sort()
|
||||
results = []
|
||||
|
||||
for i in range(len(all_ticks)):
|
||||
for j in range(len(tracks[all_ticks[i]])):
|
||||
results.append(
|
||||
SingleCommand(
|
||||
tracks[all_ticks[i]][j],
|
||||
tick_delay=(
|
||||
(
|
||||
0
|
||||
if (
|
||||
(all_ticks[i + 1] - all_ticks[i])
|
||||
/ len(tracks[all_ticks[i]])
|
||||
< 1
|
||||
)
|
||||
else 1
|
||||
)
|
||||
if j != 0
|
||||
else (
|
||||
(
|
||||
all_ticks[i]
|
||||
- all_ticks[i - 1]
|
||||
- (
|
||||
0
|
||||
if (
|
||||
(all_ticks[i] - all_ticks[i - 1])
|
||||
/ len(tracks[all_ticks[i - 1]])
|
||||
< 1
|
||||
)
|
||||
else (len(tracks[all_ticks[i - 1]]) - 1)
|
||||
)
|
||||
)
|
||||
if i != 0
|
||||
else all_ticks[i]
|
||||
)
|
||||
),
|
||||
annotation="在{}播放{}%的{}音".format(
|
||||
mctick2timestr(
|
||||
i + 0
|
||||
if (
|
||||
(all_ticks[i + 1] - all_ticks[i])
|
||||
/ len(tracks[all_ticks[i]])
|
||||
< 1
|
||||
)
|
||||
else j
|
||||
),
|
||||
max_volume * 100,
|
||||
"",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
self.music_command_list = results
|
||||
self.music_tick_num = max(all_ticks)
|
||||
return results, self.music_tick_num
|
||||
|
||||
|
||||
class FutureMidiConvertM6(MidiConvert):
|
||||
"""
|
||||
加入插值算法优化音感,但仅用于第一音轨
|
||||
"""
|
||||
|
||||
# TODO 没写完的!!!!
|
||||
1028
Musicreater/main.py
1028
Musicreater/main.py
File diff suppressed because it is too large
Load Diff
583
Musicreater/paramcurve.py
Normal file
583
Musicreater/paramcurve.py
Normal file
@@ -0,0 +1,583 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储 音·创 v3 内部数据使用的参数曲线
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2026 金羿
|
||||
Copyright © 2026 Eilles
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
# WARNING 本文件所含之功能未经完整测试
|
||||
# 鉴于白谭若佬给出的建议:本功能应是处于低优先级开发的
|
||||
# 因此暂时用处不大,可以稍微放一会再进行开发
|
||||
# 目前用人工智能生成了部分代码,只经过简单的测试
|
||||
# 可以等伶伦工作站开发出来后再进行完整的测试
|
||||
|
||||
|
||||
from math import ceil
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Any, List, Tuple
|
||||
from enum import Enum
|
||||
import bisect
|
||||
|
||||
from .types import FittingFunctionType
|
||||
|
||||
|
||||
def _evaluate_bezier_segment(
|
||||
t0: float,
|
||||
v0: float,
|
||||
t1: float,
|
||||
v1: float,
|
||||
out_tangent: Optional[Tuple[float, float]],
|
||||
in_tangent: Optional[Tuple[float, float]],
|
||||
u: float,
|
||||
) -> float:
|
||||
"""
|
||||
计算贝塞尔区间 [t0, t1] 在归一化参数 u ∈ [0,1] 处的 y 值。
|
||||
|
||||
控制点:
|
||||
P0 = (t0, v0)
|
||||
P1 = (t0 + out_dt, v0 + out_dv)
|
||||
P2 = (t1 - in_dt, v1 - in_dv) ← 注意:in_tangent 是相对于 t1 的偏移
|
||||
P3 = (t1, v1)
|
||||
"""
|
||||
# 默认控制点:退化为线性
|
||||
p0 = (t0, v0)
|
||||
p3 = (t1, v1)
|
||||
|
||||
if out_tangent is not None:
|
||||
p1 = (t0 + out_tangent[0], v0 + out_tangent[1])
|
||||
else:
|
||||
p1 = p0 # 无出手柄 → 与起点重合
|
||||
|
||||
if in_tangent is not None:
|
||||
p2 = (t1 - in_tangent[0], v1 - in_tangent[1])
|
||||
else:
|
||||
p2 = p3 # 无入手柄 → 与终点重合
|
||||
|
||||
# 三次贝塞尔 y(t)
|
||||
mt = 1.0 - u
|
||||
return mt**3 * p0[1] + 3 * mt**2 * u * p1[1] + 3 * mt * u**2 * p2[1] + u**3 * p3[1]
|
||||
|
||||
|
||||
class InterpolationMethod:
|
||||
"""
|
||||
预定义的标准化插值函数集合。所有函数接受归一化输入 u ∈ [0,1],返回 v ∈ [0,1]。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def linear(u: float) -> float:
|
||||
"""
|
||||
线性插值。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
u : float
|
||||
归一化时间,范围 [0, 1]。
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
插值权重,范围 [0, 1]。
|
||||
"""
|
||||
return u
|
||||
|
||||
@staticmethod
|
||||
def ease_in_quad(u: float) -> float:
|
||||
"""
|
||||
二次缓入(慢进快出)。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
u : float
|
||||
归一化时间,范围 [0, 1]。
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
插值权重。
|
||||
"""
|
||||
return u * u
|
||||
|
||||
@staticmethod
|
||||
def ease_out_quad(u: float) -> float:
|
||||
"""
|
||||
二次缓出(快进慢出)。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
u : float
|
||||
归一化时间,范围 [0, 1]。
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
插值权重。
|
||||
"""
|
||||
return 1 - (1 - u) ** 2
|
||||
|
||||
@staticmethod
|
||||
def ease_in_out_quad(u: float) -> float:
|
||||
"""
|
||||
二次缓入缓出。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
u : float
|
||||
归一化时间,范围 [0, 1]。
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
插值权重。
|
||||
"""
|
||||
if u < 0.5:
|
||||
return 2 * u * u
|
||||
else:
|
||||
return 1 - pow(-2 * u + 2, 2) / 2
|
||||
|
||||
@staticmethod
|
||||
def hold(u: float) -> float:
|
||||
"""
|
||||
阶梯保持模式占位函数。实际插值逻辑在 ParamCurve.value_at 中特殊处理。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
u : float
|
||||
归一化时间(忽略)。
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
无意义,仅作标识。
|
||||
"""
|
||||
return 0.0
|
||||
|
||||
|
||||
@dataclass
|
||||
class Keyframe:
|
||||
"""
|
||||
参数曲线上的一个关键帧,支持完整的入/出切线控制。
|
||||
|
||||
插值优先级:
|
||||
1. 若 use_bezier=True → 使用贝塞尔模式(需 in_tangent / out_tangent)
|
||||
2. 否则 → 使用 out_interp 函数(in_interp 被忽略)
|
||||
"""
|
||||
|
||||
time: float
|
||||
value: float
|
||||
|
||||
# 函数插值模式
|
||||
out_interp: Optional[FittingFunctionType] = None
|
||||
|
||||
# 贝塞尔模式
|
||||
in_tangent: Optional[Tuple[float, float]] = (
|
||||
None # (dt, dv) ← 相对于自身(负 dt 表示左侧)
|
||||
)
|
||||
out_tangent: Optional[Tuple[float, float]] = (
|
||||
None # (dt, dv) → 相对于自身(正 dt 表示右侧)
|
||||
)
|
||||
use_bezier: bool = False
|
||||
|
||||
|
||||
class BoundaryBehaviour(str, Enum):
|
||||
"""
|
||||
边界行为枚举。
|
||||
"""
|
||||
|
||||
CONSTANT = "constant"
|
||||
"""返回默认基线值"""
|
||||
HOLD = "hold"
|
||||
"""保持首/尾关键帧的值"""
|
||||
|
||||
|
||||
class ParamCurve:
|
||||
"""
|
||||
参数曲线类
|
||||
"""
|
||||
|
||||
"""
|
||||
支持动态节点编辑
|
||||
用户通过添加/修改关键帧(时间-值对)来定义曲线,类自动在相邻关键帧之间生成插值段。
|
||||
支持多种插值模式:线性('linear')、平滑缓动('smooth')、保持('hold')或自定义函数。
|
||||
"""
|
||||
|
||||
base_line: float = 0.0
|
||||
"""基线/默认值"""
|
||||
|
||||
base_interpolation_function: FittingFunctionType
|
||||
"""默认(未指定区间时的)关键帧插值模式"""
|
||||
|
||||
boundary_behaviour: BoundaryBehaviour
|
||||
"""边界行为,控制参数曲线在已定义的范围外的返回值"""
|
||||
|
||||
_keys: List[Keyframe]
|
||||
"""关键帧列表"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_value: float = 0.0,
|
||||
default_interpolation_function: FittingFunctionType = InterpolationMethod.linear,
|
||||
boundary_mode: BoundaryBehaviour = BoundaryBehaviour.CONSTANT,
|
||||
):
|
||||
"""
|
||||
初始化参数曲线。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
base_value : float
|
||||
边界外默认值(当 boundary_mode 为 BoundaryBehaviour.CONSTANT 时使用)。
|
||||
default_interpolation_function : FittingFunctionType
|
||||
新关键帧的默认 out_interp。
|
||||
boundary_mode : BoundaryBehaviour
|
||||
范围外行为:
|
||||
- BoundaryBehaviour.CONSTANT: 返回 base_value
|
||||
- BoundaryBehaviour.HOLD: 保持首/尾关键帧值
|
||||
"""
|
||||
self.base_line = base_value
|
||||
self.base_interpolation_function = default_interpolation_function
|
||||
self.boundary_behaviour = boundary_mode
|
||||
|
||||
self._keys: List[Keyframe] = []
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self._keys) or (self.base_line != 0)
|
||||
|
||||
def add_key(
|
||||
self,
|
||||
time: float,
|
||||
value: float,
|
||||
out_interp: Optional[FittingFunctionType] = None,
|
||||
in_tangent: Optional[Tuple[float, float]] = None,
|
||||
out_tangent: Optional[Tuple[float, float]] = None,
|
||||
use_bezier: bool = False,
|
||||
):
|
||||
"""
|
||||
添加或更新关键帧。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
time : float
|
||||
关键帧时间。
|
||||
value : float
|
||||
参数值。
|
||||
out_interp : Optional[Callable]
|
||||
出插值函数(若 use_bezier=False)。
|
||||
in_tangent : Optional[Tuple[float, float]]
|
||||
入切线偏移 (dt, dv)。dt 通常为负(表示左侧),但存储为绝对偏移。
|
||||
out_tangent : Optional[Tuple[float, float]]
|
||||
出切线偏移 (dt, dv)。dt 通常为正。
|
||||
use_bezier : bool
|
||||
是否使用贝塞尔插值。
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
|
||||
Notes
|
||||
-----
|
||||
若时间已存在,更新该关键帧的所有属性。
|
||||
"""
|
||||
interp = (
|
||||
out_interp if out_interp is not None else self.base_interpolation_function
|
||||
)
|
||||
new_key = Keyframe(time, value, interp, in_tangent, out_tangent, use_bezier)
|
||||
|
||||
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||
self._keys[idx] = new_key
|
||||
else:
|
||||
self._keys.insert(idx, new_key)
|
||||
|
||||
def remove_key(self, time: float):
|
||||
"""
|
||||
移除指定时间的关键帧。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
time : float
|
||||
要移除的关键帧时间。
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||
del self._keys[idx]
|
||||
|
||||
def update_key_value(self, time: float, new_value: float):
|
||||
"""更新关键帧值,保留其他属性。"""
|
||||
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||
k = self._keys[idx]
|
||||
self._keys[idx] = Keyframe(
|
||||
time, new_value, k.out_interp, k.in_tangent, k.out_tangent, k.use_bezier
|
||||
)
|
||||
|
||||
def update_key_interp(
|
||||
self,
|
||||
time: float,
|
||||
out_interp: Optional[FittingFunctionType] = None,
|
||||
in_tangent: Optional[Tuple[float, float]] = None,
|
||||
out_tangent: Optional[Tuple[float, float]] = None,
|
||||
use_bezier: bool = False,
|
||||
):
|
||||
"""更新关键帧的插值属性。"""
|
||||
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||
k = self._keys[idx]
|
||||
new_value = k.value
|
||||
interp = out_interp if out_interp is not None else k.out_interp
|
||||
self._keys[idx] = Keyframe(
|
||||
time, new_value, interp, in_tangent, out_tangent, use_bezier
|
||||
)
|
||||
|
||||
def set_key_tangents(
|
||||
self,
|
||||
time: float,
|
||||
in_tangent: Optional[Tuple[float, float]] = None,
|
||||
out_tangent: Optional[Tuple[float, float]] = None,
|
||||
use_bezier: bool = True,
|
||||
):
|
||||
"""单独设置关键帧的切线,不改变值。"""
|
||||
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||
k = self._keys[idx]
|
||||
self._keys[idx] = Keyframe(
|
||||
time,
|
||||
k.value,
|
||||
out_interp=k.out_interp,
|
||||
in_tangent=in_tangent,
|
||||
out_tangent=out_tangent,
|
||||
use_bezier=use_bezier,
|
||||
)
|
||||
|
||||
def make_key_smooth(self, time: float):
|
||||
"""
|
||||
将关键帧设为“平滑”模式(自动对称切线,并设为贝塞尔模式)。
|
||||
切线长度基于相邻关键帧的时间和值差。
|
||||
"""
|
||||
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||
k = self._keys[idx]
|
||||
prev_k = self._keys[idx - 1] if idx > 0 else None
|
||||
next_k = self._keys[idx + 1] if idx + 1 < len(self._keys) else None
|
||||
|
||||
# 默认切线长度:时间差的 1/3,值差按比例
|
||||
dt_in = dt_out = 0.1
|
||||
dv_in = dv_out = 0.0
|
||||
|
||||
if prev_k and next_k:
|
||||
dt_total = next_k.time - prev_k.time
|
||||
dv_total = next_k.value - prev_k.value
|
||||
dt_in = dt_out = dt_total / 3.0
|
||||
dv_in = dv_out = dv_total / 3.0
|
||||
elif prev_k:
|
||||
dt_out = (k.time - prev_k.time) / 2.0
|
||||
dv_out = (k.value - prev_k.value) / 2.0
|
||||
dt_in = dt_out
|
||||
dv_in = dv_out
|
||||
elif next_k:
|
||||
dt_in = (next_k.time - k.time) / 2.0
|
||||
dv_in = (next_k.value - k.value) / 2.0
|
||||
dt_out = dt_in
|
||||
dv_out = dv_in
|
||||
|
||||
self.set_key_tangents(
|
||||
time,
|
||||
in_tangent=(-dt_in, -dv_in), # in_tangent 存储为偏移,使用时做减法
|
||||
out_tangent=(dt_out, dv_out),
|
||||
use_bezier=True,
|
||||
)
|
||||
|
||||
def _get_boundary_value(self, t: float) -> float:
|
||||
"""根据 boundary_mode 获取范围外的值。"""
|
||||
if not self._keys:
|
||||
return self.base_line
|
||||
if self.boundary_behaviour == BoundaryBehaviour.CONSTANT:
|
||||
return self.base_line
|
||||
elif self.boundary_behaviour == BoundaryBehaviour.HOLD:
|
||||
if t < self._keys[0].time:
|
||||
return self._keys[0].value
|
||||
else:
|
||||
return self._keys[-1].value
|
||||
else: # 可能会有别的模式吗?
|
||||
return self.base_line
|
||||
|
||||
def value_at(self, t: float) -> float:
|
||||
"""
|
||||
计算时间 t 处的曲线值。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
t : float
|
||||
查询时间。
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
插值结果。
|
||||
"""
|
||||
keys = self._keys
|
||||
if not keys:
|
||||
return self._get_boundary_value(t)
|
||||
|
||||
if t < keys[0].time or t > keys[-1].time:
|
||||
return self._get_boundary_value(t)
|
||||
|
||||
times = [k.time for k in keys]
|
||||
idx = bisect.bisect_right(times, t) - 1
|
||||
|
||||
if idx < 0:
|
||||
return self._get_boundary_value(t)
|
||||
if idx >= len(keys) - 1:
|
||||
return keys[-1].value
|
||||
|
||||
k0 = keys[idx]
|
||||
k1 = keys[idx + 1]
|
||||
|
||||
if k0.time == k1.time:
|
||||
return k0.value
|
||||
if k0.time == t:
|
||||
return k0.value
|
||||
if k1.time == t:
|
||||
return k1.value
|
||||
|
||||
t0, v0 = k0.time, k0.value
|
||||
t1, v1 = k1.time, k1.value
|
||||
u = (t - t0) / (t1 - t0)
|
||||
u = max(0.0, min(1.0, u))
|
||||
|
||||
# 贝塞尔模式(高优先级)
|
||||
if k0.use_bezier or k1.use_bezier:
|
||||
return _evaluate_bezier_segment(
|
||||
t0,
|
||||
v0,
|
||||
t1,
|
||||
v1,
|
||||
out_tangent=k0.out_tangent,
|
||||
in_tangent=k1.in_tangent, # ← 关键:使用下一帧的 in_tangent!
|
||||
u=u,
|
||||
)
|
||||
|
||||
# 函数插值模式,优先处理阶梯保持模式
|
||||
elif k0.out_interp is InterpolationMethod.hold:
|
||||
return v0
|
||||
|
||||
interp_func = k0.out_interp or self.base_interpolation_function
|
||||
v_norm = interp_func(u)
|
||||
return v0 + v_norm * (v1 - v0)
|
||||
|
||||
def __call__(self, t: float) -> float:
|
||||
return self.value_at(t)
|
||||
|
||||
def get_all_keys(self) -> List[Tuple[float, float]]:
|
||||
"""返回 (time, value) 列表。"""
|
||||
return [(k.time, k.value) for k in self._keys]
|
||||
|
||||
def set_default_interpolation_function(self, interp_func: FittingFunctionType):
|
||||
"""设置默认插值函数。"""
|
||||
self.base_interpolation_function = interp_func
|
||||
|
||||
def set_boundary_mode(
|
||||
self, mode: BoundaryBehaviour, base_value: Optional[float] = None
|
||||
):
|
||||
"""
|
||||
设置边界行为。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mode : BoundaryBehaviour
|
||||
边界行为设定
|
||||
base_value : Optional[float]
|
||||
当 mode=BoundaryBehaviour.CONSTANT 时,指定新的默认值。
|
||||
"""
|
||||
self.boundary_behaviour = mode
|
||||
if base_value is not None:
|
||||
self.base_line = base_value
|
||||
|
||||
def bake(
|
||||
self,
|
||||
start: float,
|
||||
end: float,
|
||||
sample_rate: Optional[float] = None,
|
||||
num_samples: Optional[int] = None,
|
||||
dtype: Any = None,
|
||||
) -> "np.ndarray": # type: ignore 这里这样用会报错吗?不知道,但是人工智能这样写了都,大抵是能用的吧
|
||||
"""
|
||||
将参数曲线在指定时间范围内烘焙为 NumPy 数组,用于高性能实时查询或音频渲染。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : float
|
||||
烘焙起始时间(包含)。
|
||||
end : float
|
||||
烘焙结束时间(不包含)。
|
||||
sample_rate : Optional[float]
|
||||
采样率(单位:样本/时间单位)。例如,若时间单位为秒,sample_rate=48000 表示每秒 48k 样本。
|
||||
必须与 `num_samples` 二选一提供。
|
||||
num_samples : Optional[int]
|
||||
输出数组的总样本数。若提供,则忽略 `sample_rate`。
|
||||
dtype : Any, optional
|
||||
输出数组的数据类型(如 np.float32)。默认为 np.float64。
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
一维 NumPy 数组,长度为 `num_samples`,`arr[i] ≈ curve(start + i / sample_rate)`。
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
ValueError
|
||||
- 若 `start >= end`
|
||||
- 若未提供 `sample_rate` 且未提供 `num_samples`
|
||||
- 若 `num_samples <= 0`
|
||||
|
||||
Notes
|
||||
-----
|
||||
- 内部使用 `np.linspace` 生成时间轴,然后逐点调用 `self.value_at(t)`。
|
||||
- 虽然目前是 Python 循环,但对于典型自动化曲线(<1000 关键帧),NumPy 向量化优势主要体现在内存布局和后续处理。
|
||||
- 如需极致性能(如 >1M 样本),可未来优化为 C++/Numba 加速,但当前已满足 DAW 自动化需求。
|
||||
"""
|
||||
if start >= end:
|
||||
raise ValueError("起始值须小于结束值。")
|
||||
|
||||
if num_samples is not None:
|
||||
if num_samples <= 0:
|
||||
raise ValueError("烘焙的采样数须为非零自然数。")
|
||||
n = num_samples
|
||||
elif sample_rate is not None:
|
||||
if sample_rate <= 0:
|
||||
raise ValueError("烘焙的采样率须为正值。")
|
||||
duration = end - start
|
||||
n = int(ceil(duration * sample_rate))
|
||||
# 别因为小数数值会产生的问题而越界了来着
|
||||
if n == 0:
|
||||
n = 1
|
||||
else:
|
||||
raise ValueError("烘焙参数时,须提供采样率或采样数。")
|
||||
|
||||
import numpy as np
|
||||
|
||||
# 生成对应时间的节点:[start, ..., end - dt]
|
||||
times = np.linspace(start, end, n, endpoint=False)
|
||||
|
||||
# 计算每个时间节点上的参数值
|
||||
# 我们认为在数字音频工作站的环境里,此值可能最多到 ~1e6 的样子,因此这样 for 一下应当可以接受
|
||||
# WARNING: 人工智能是这样理解的,如果有问题的话后续可能需要更改
|
||||
values = np.empty(n, dtype=dtype or np.float64)
|
||||
for i in range(n):
|
||||
values[i] = self.value_at(float(times[i]))
|
||||
|
||||
return values
|
||||
@@ -1,543 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from typing import Tuple
|
||||
|
||||
from TrimMCStruct import Structure
|
||||
|
||||
from ...main import MidiConvert
|
||||
from ..archive import behavior_mcpack_manifest, compress_zipfile
|
||||
from ..main import ConvertConfig
|
||||
from ..mcstructure import (
|
||||
COMPABILITY_VERSION_117,
|
||||
COMPABILITY_VERSION_119,
|
||||
commands_to_redstone_delay_structure,
|
||||
commands_to_structure,
|
||||
form_command_block_in_NBT_struct,
|
||||
)
|
||||
|
||||
|
||||
def to_addon_pack_in_score(
|
||||
midi_cvt: MidiConvert,
|
||||
data_cfg: ConvertConfig,
|
||||
scoreboard_name: str = "mscplay",
|
||||
auto_reset: bool = False,
|
||||
) -> Tuple[int, int]:
|
||||
"""
|
||||
将midi以计分播放器形式转换为我的世界函数附加包
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
data_cfg: ConvertConfig 对象
|
||||
部分转换通用参数
|
||||
scoreboard_name: str
|
||||
我的世界的计分板名称
|
||||
auto_reset: bool
|
||||
是否自动重置计分板
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[int指令数量, int音乐总延迟]
|
||||
"""
|
||||
|
||||
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_score(
|
||||
scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier
|
||||
)
|
||||
|
||||
# 当文件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")
|
||||
|
||||
# 写入manifest.json
|
||||
with open(f"{data_cfg.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 + "播放",
|
||||
modules_description=f"无 - 由 音·创 生成",
|
||||
),
|
||||
fp=f,
|
||||
indent=4,
|
||||
)
|
||||
|
||||
# 写入stop.mcfunction
|
||||
with open(
|
||||
f"{data_cfg.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"
|
||||
)
|
||||
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",
|
||||
"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[scores={"
|
||||
+ scoreboard_name
|
||||
+ "=1..}] "
|
||||
+ 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 "",
|
||||
)
|
||||
)
|
||||
|
||||
if data_cfg.progressbar_style:
|
||||
with open(
|
||||
f"{data_cfg.dist_path}/temp/functions/mscplay/progressShow.mcfunction",
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
f.writelines(
|
||||
"\n".join(
|
||||
[
|
||||
single_cmd.cmd
|
||||
for single_cmd in midi_cvt.form_progress_bar(
|
||||
maxscore, scoreboard_name, data_cfg.progressbar_style
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
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")
|
||||
compress_zipfile(
|
||||
f"{data_cfg.dist_path}/temp/",
|
||||
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
|
||||
)
|
||||
|
||||
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
|
||||
|
||||
return maxlen, maxscore
|
||||
|
||||
|
||||
def to_addon_pack_in_delay(
|
||||
midi_cvt: MidiConvert,
|
||||
data_cfg: ConvertConfig,
|
||||
player: str = "@a",
|
||||
max_height: int = 64,
|
||||
) -> Tuple[int, int]:
|
||||
"""
|
||||
将midi以延迟播放器形式转换为mcstructure结构文件后打包成附加包,并在附加包中生成相应地导入函数
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
data_cfg: ConvertConfig 对象
|
||||
部分转换通用参数
|
||||
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
|
||||
)
|
||||
|
||||
command_list, max_delay = midi_cvt.to_command_list_in_delay(
|
||||
data_cfg.volume_ratio,
|
||||
data_cfg.speed_multiplier,
|
||||
player,
|
||||
)[:2]
|
||||
|
||||
if not os.path.exists(data_cfg.dist_path):
|
||||
os.makedirs(data_cfg.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/")
|
||||
|
||||
# 写入manifest.json
|
||||
with open(f"{data_cfg.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 + "播放",
|
||||
modules_description=f"无 - 由 音·创 生成",
|
||||
),
|
||||
fp=f,
|
||||
indent=4,
|
||||
)
|
||||
|
||||
# 写入stop.mcfunction
|
||||
with open(
|
||||
f"{data_cfg.dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
|
||||
) as f:
|
||||
f.write(
|
||||
"gamerule commandblocksenabled false\ngamerule commandblocksenabled true"
|
||||
)
|
||||
|
||||
# 将命令列表写入文件
|
||||
index_file = open(
|
||||
f"{data_cfg.dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
|
||||
)
|
||||
|
||||
struct, size, end_pos = commands_to_structure(
|
||||
command_list,
|
||||
max_height - 1,
|
||||
compability_version_=compability_ver,
|
||||
)
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
data_cfg.dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.midi_music_name}_main.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct.dump(f)
|
||||
|
||||
del struct
|
||||
|
||||
if data_cfg.progressbar_style:
|
||||
scb_name = midi_cvt.midi_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)
|
||||
struct_a.set_block(
|
||||
(0, 0, 0),
|
||||
form_command_block_in_NBT_struct(
|
||||
r"scoreboard players add {} {} 1".format(player, scb_name),
|
||||
(0, 0, 0),
|
||||
1,
|
||||
1,
|
||||
alwaysRun=False,
|
||||
customName="显示进度条并加分",
|
||||
compability_version_number=compability_ver,
|
||||
),
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
data_cfg.dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.midi_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")
|
||||
|
||||
pgb_struct, pgbSize, pgbNowPos = commands_to_structure(
|
||||
midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style),
|
||||
max_height - 1,
|
||||
compability_version_=compability_ver,
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
data_cfg.dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.midi_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")
|
||||
|
||||
struct_a = Structure(
|
||||
(1, 1, 1),
|
||||
)
|
||||
struct_a.set_block(
|
||||
(0, 0, 0),
|
||||
form_command_block_in_NBT_struct(
|
||||
r"scoreboard players reset {} {}".format(player, scb_name),
|
||||
(0, 0, 0),
|
||||
1,
|
||||
0,
|
||||
alwaysRun=False,
|
||||
customName="重置进度条计分板",
|
||||
compability_version_number=compability_ver,
|
||||
),
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
data_cfg.dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.midi_music_name}_reset.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct_a.dump(f)
|
||||
|
||||
del struct_a, pgb_struct
|
||||
|
||||
index_file.write(
|
||||
f"structure load {midi_cvt.midi_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"
|
||||
)
|
||||
|
||||
else:
|
||||
index_file.write(f"structure load {midi_cvt.midi_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")
|
||||
compress_zipfile(
|
||||
f"{data_cfg.dist_path}/temp/",
|
||||
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
|
||||
)
|
||||
|
||||
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
|
||||
|
||||
return len(command_list), max_delay
|
||||
|
||||
|
||||
def to_addon_pack_in_repeater(
|
||||
midi_cvt: MidiConvert,
|
||||
data_cfg: ConvertConfig,
|
||||
player: str = "@a",
|
||||
max_height: int = 65,
|
||||
) -> Tuple[int, int]:
|
||||
"""
|
||||
将midi以中继器播放器形式转换为mcstructure结构文件后打包成附加包,并在附加包中生成相应地导入函数
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
data_cfg: ConvertConfig 对象
|
||||
部分转换通用参数
|
||||
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
|
||||
)
|
||||
|
||||
command_list, max_delay, max_together = midi_cvt.to_command_list_in_delay(
|
||||
data_cfg.volume_ratio,
|
||||
data_cfg.speed_multiplier,
|
||||
player,
|
||||
)
|
||||
|
||||
if not os.path.exists(data_cfg.dist_path):
|
||||
os.makedirs(data_cfg.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/")
|
||||
|
||||
# 写入manifest.json
|
||||
with open(f"{data_cfg.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 + "播放",
|
||||
modules_description=f"无 - 由 音·创 生成",
|
||||
),
|
||||
fp=f,
|
||||
indent=4,
|
||||
)
|
||||
|
||||
# 写入stop.mcfunction
|
||||
with open(
|
||||
f"{data_cfg.dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
|
||||
) as f:
|
||||
f.write(
|
||||
"gamerule commandblocksenabled false\ngamerule commandblocksenabled true"
|
||||
)
|
||||
|
||||
# 将命令列表写入文件
|
||||
index_file = open(
|
||||
f"{data_cfg.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,
|
||||
compability_version_=compability_ver,
|
||||
)
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
data_cfg.dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.midi_music_name}_main.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct.dump(f)
|
||||
|
||||
del struct
|
||||
|
||||
if data_cfg.progressbar_style:
|
||||
scb_name = midi_cvt.midi_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)
|
||||
struct_a.set_block(
|
||||
(0, 0, 0),
|
||||
form_command_block_in_NBT_struct(
|
||||
r"scoreboard players add {} {} 1".format(player, scb_name),
|
||||
(0, 0, 0),
|
||||
1,
|
||||
1,
|
||||
alwaysRun=False,
|
||||
customName="显示进度条并加分",
|
||||
compability_version_number=compability_ver,
|
||||
),
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
data_cfg.dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.midi_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")
|
||||
|
||||
pgb_struct, pgbSize, pgbNowPos = commands_to_structure(
|
||||
midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style),
|
||||
max_height - 1,
|
||||
compability_version_=compability_ver,
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
data_cfg.dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.midi_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")
|
||||
|
||||
struct_a = Structure(
|
||||
(1, 1, 1),
|
||||
)
|
||||
struct_a.set_block(
|
||||
(0, 0, 0),
|
||||
form_command_block_in_NBT_struct(
|
||||
r"scoreboard players reset {} {}".format(player, scb_name),
|
||||
(0, 0, 0),
|
||||
1,
|
||||
0,
|
||||
alwaysRun=False,
|
||||
customName="重置进度条计分板",
|
||||
compability_version_number=compability_ver,
|
||||
),
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
data_cfg.dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.midi_music_name}_reset.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct_a.dump(f)
|
||||
|
||||
del struct_a, pgb_struct
|
||||
|
||||
index_file.write(
|
||||
f"structure load {midi_cvt.midi_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"
|
||||
)
|
||||
|
||||
else:
|
||||
index_file.write(f"structure load {midi_cvt.midi_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")
|
||||
compress_zipfile(
|
||||
f"{data_cfg.dist_path}/temp/",
|
||||
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
|
||||
)
|
||||
|
||||
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
|
||||
|
||||
return len(command_list), max_delay
|
||||
@@ -1,27 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放通用的普遍性的插件内容
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
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)))
|
||||
@@ -1,106 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放附加内容功能
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Tuple, Union
|
||||
|
||||
from ..subclass import DEFAULT_PROGRESSBAR_STYLE, ProgressBarStyle
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ConvertConfig:
|
||||
"""
|
||||
转换通用设置存储类
|
||||
"""
|
||||
|
||||
volume_ratio: float
|
||||
"""音量比例"""
|
||||
|
||||
speed_multiplier: float
|
||||
"""速度倍率"""
|
||||
|
||||
progressbar_style: Union[ProgressBarStyle, None]
|
||||
"""进度条样式"""
|
||||
|
||||
dist_path: str
|
||||
"""输出目录"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
output_path: str,
|
||||
volume: float = 1.0,
|
||||
speed: float = 1.0,
|
||||
progressbar: Union[bool, Tuple[str, Tuple[str, str]], ProgressBarStyle] = True,
|
||||
ignore_progressbar_param_error: bool = False,
|
||||
):
|
||||
"""
|
||||
将已经转换好的数据内容指令载入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
|
||||
"""输出目录"""
|
||||
|
||||
self.volume_ratio = volume
|
||||
"""音量比例"""
|
||||
|
||||
self.speed_multiplier = speed
|
||||
"""速度倍率"""
|
||||
|
||||
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.progressbar_style = None
|
||||
"""进度条样式组"""
|
||||
@@ -1,40 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放有关WebSocket服务器操作的内容
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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))
|
||||
@@ -1,29 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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
|
||||
|
||||
from ...main import MidiConvert
|
||||
|
||||
from ..main import ConvertConfig
|
||||
from ...subclass import SingleCommand
|
||||
|
||||
|
||||
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)
|
||||
424
Musicreater/plugins.py
Normal file
424
Musicreater/plugins.py
Normal file
@@ -0,0 +1,424 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储 音·创 v3 的插件接口与管理相关内容
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2026 金羿
|
||||
Copyright © 2026 Eilles
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Dict,
|
||||
Any,
|
||||
Optional,
|
||||
List,
|
||||
Tuple,
|
||||
Union,
|
||||
Generator,
|
||||
Set,
|
||||
Iterable,
|
||||
Iterator,
|
||||
TypeVar,
|
||||
Mapping,
|
||||
Callable,
|
||||
)
|
||||
from itertools import chain
|
||||
|
||||
|
||||
from ._plugin_abc import (
|
||||
# 枚举类
|
||||
PluginTypes,
|
||||
# 抽象基类/数据类(插件参数定义)
|
||||
PluginConfig,
|
||||
PluginMetaInformation,
|
||||
# 抽象基类(插件定义)
|
||||
MusicInputPluginBase,
|
||||
TrackInputPluginBase,
|
||||
MusicOperatePluginBase,
|
||||
TrackOperatePluginBase,
|
||||
MusicOutputPluginBase,
|
||||
TrackOutputPluginBase,
|
||||
ServicePluginBase,
|
||||
LibraryPluginBase,
|
||||
# 顶层插件定义
|
||||
TopPluginBase,
|
||||
)
|
||||
from .exceptions import (
|
||||
PluginMetainfoNotFoundError,
|
||||
ParameterTypeError,
|
||||
PluginInstanceNotFoundError,
|
||||
PluginRegisteredError,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
# 枚举类
|
||||
"PluginTypes",
|
||||
# 抽象基类/数据类(插件参数定义)
|
||||
"PluginConfig",
|
||||
"PluginMetaInformation",
|
||||
# 抽象基类(插件定义)
|
||||
"MusicInputPluginBase",
|
||||
"TrackInputPluginBase",
|
||||
"MusicOperatePluginBase",
|
||||
"TrackOperatePluginBase",
|
||||
"MusicOutputPluginBase",
|
||||
"TrackOutputPluginBase",
|
||||
"ServicePluginBase",
|
||||
"LibraryPluginBase",
|
||||
# 插件注册用装饰函数
|
||||
"music_input_plugin",
|
||||
"track_input_plugin",
|
||||
"music_operate_plugin",
|
||||
"track_operate_plugin",
|
||||
"music_output_plugin",
|
||||
"track_output_plugin",
|
||||
"service_plugin",
|
||||
"library_plugin",
|
||||
]
|
||||
|
||||
|
||||
T_IOPlugin = TypeVar(
|
||||
"T_IOPlugin",
|
||||
MusicInputPluginBase,
|
||||
TrackInputPluginBase,
|
||||
MusicOutputPluginBase,
|
||||
TrackOutputPluginBase,
|
||||
)
|
||||
T_Plugin = TypeVar(
|
||||
"T_Plugin",
|
||||
MusicInputPluginBase,
|
||||
TrackInputPluginBase,
|
||||
MusicOperatePluginBase,
|
||||
TrackOperatePluginBase,
|
||||
MusicOutputPluginBase,
|
||||
TrackOutputPluginBase,
|
||||
ServicePluginBase,
|
||||
LibraryPluginBase,
|
||||
)
|
||||
|
||||
|
||||
def load_plugin_module(package: Union[Path, str]):
|
||||
"""自动发现并加载插件包中的插件
|
||||
|
||||
参数:
|
||||
=====
|
||||
package: Path | str, 可选
|
||||
插件包路径或名称,当为 Path 类时为路径,为 str 时为包名,切勿混淆。
|
||||
"""
|
||||
|
||||
if isinstance(package, Path):
|
||||
relative_path = package.resolve().relative_to(Path.cwd().resolve())
|
||||
if relative_path.stem == "__init__":
|
||||
return importlib.import_module(".".join(relative_path.parts[:-1]))
|
||||
else:
|
||||
return importlib.import_module(
|
||||
".".join(relative_path.parts[:-1] + (relative_path.stem,))
|
||||
)
|
||||
else:
|
||||
return importlib.import_module(package)
|
||||
|
||||
|
||||
class PluginRegistry:
|
||||
"""插件注册管理器(注册表)"""
|
||||
|
||||
def __init__(self):
|
||||
self._music_input_plugins: Dict[str, MusicInputPluginBase] = {}
|
||||
self._track_input_plugins: Dict[str, TrackInputPluginBase] = {}
|
||||
self._music_operate_plugins: Dict[str, MusicOperatePluginBase] = {}
|
||||
self._track_operate_plugins: Dict[str, TrackOperatePluginBase] = {}
|
||||
self._music_output_plugins: Dict[str, MusicOutputPluginBase] = {}
|
||||
self._track_output_plugins: Dict[str, TrackOutputPluginBase] = {}
|
||||
self._service_plugins: Dict[str, ServicePluginBase] = {}
|
||||
self._library_plugins: Dict[str, LibraryPluginBase] = {}
|
||||
|
||||
def __iter__(self) -> Iterator[Tuple[PluginTypes, Mapping[str, TopPluginBase]]]:
|
||||
"""迭代器,返回所有插件"""
|
||||
return iter(
|
||||
(
|
||||
(PluginTypes.FUNCTION_MUSIC_IMPORT, self._music_input_plugins),
|
||||
(PluginTypes.FUNCTION_TRACK_IMPORT, self._track_input_plugins),
|
||||
(PluginTypes.FUNCTION_MUSIC_OPERATE, self._music_operate_plugins),
|
||||
(PluginTypes.FUNCTION_TRACK_OPERATE, self._track_operate_plugins),
|
||||
(PluginTypes.FUNCTION_MUSIC_EXPORT, self._music_output_plugins),
|
||||
(PluginTypes.FUNCTION_TRACK_EXPORT, self._track_output_plugins),
|
||||
(PluginTypes.SERVICE, self._service_plugins),
|
||||
(PluginTypes.LIBRARY, self._library_plugins),
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _register_plugin(cls_dict: dict, plg_class: type, plg_id: str) -> None:
|
||||
"""注册插件"""
|
||||
if plg_id in cls_dict:
|
||||
if cls_dict[plg_id].metainfo.version >= plg_class.metainfo.version:
|
||||
raise PluginRegisteredError(
|
||||
"插件惟一识别码`{}`所对应的插件已存在更高版本`{}`,请勿重复注册同一插件!".format(
|
||||
plg_id, plg_class.metainfo
|
||||
)
|
||||
)
|
||||
cls_dict[plg_id] = plg_class()
|
||||
|
||||
def register_music_input_plugin(
|
||||
self,
|
||||
plugin_class: type,
|
||||
plugin_id: str,
|
||||
) -> None:
|
||||
"""注册输入插件-整首曲目"""
|
||||
self._register_plugin(self._music_input_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_track_input_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册输入插件-单个音轨"""
|
||||
self._register_plugin(self._track_input_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_music_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册曲目处理插件"""
|
||||
self._register_plugin(self._music_operate_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_track_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册音轨处理插件"""
|
||||
self._register_plugin(self._track_operate_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_music_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册输出插件-整首曲目"""
|
||||
self._register_plugin(self._music_output_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_track_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册输出插件-单个音轨"""
|
||||
self._register_plugin(self._track_output_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_service_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册服务插件"""
|
||||
self._register_plugin(self._service_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_library_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册支持库插件"""
|
||||
self._register_plugin(self._library_plugins, plugin_class, plugin_id)
|
||||
|
||||
@staticmethod
|
||||
def _get_io_plugin_by_format(
|
||||
plugin_regdict: Dict[str, T_IOPlugin], fpath_or_format: Union[Path, str]
|
||||
) -> Generator[T_IOPlugin, None, None]:
|
||||
if isinstance(fpath_or_format, str):
|
||||
return (
|
||||
plugin
|
||||
for plugin in plugin_regdict.values()
|
||||
if plugin.can_handle_format(fpath_or_format)
|
||||
)
|
||||
elif isinstance(fpath_or_format, Path):
|
||||
return (
|
||||
plugin
|
||||
for plugin in plugin_regdict.values()
|
||||
if plugin.can_handle_file(fpath_or_format)
|
||||
)
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(fpath_or_format), fpath_or_format
|
||||
)
|
||||
)
|
||||
|
||||
def get_music_input_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[MusicInputPluginBase, None, None]:
|
||||
"""通过指定输入的文件或格式,以获取对应的全曲导入用插件"""
|
||||
return self._get_io_plugin_by_format(
|
||||
self._music_input_plugins, filepath_or_format
|
||||
)
|
||||
|
||||
def get_track_input_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[TrackInputPluginBase, None, None]:
|
||||
"""通过指定输入的文件或格式,以获取对应的单音轨导入用插件"""
|
||||
return self._get_io_plugin_by_format(
|
||||
self._track_input_plugins, filepath_or_format
|
||||
)
|
||||
|
||||
def get_music_output_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[MusicOutputPluginBase, None, None]:
|
||||
"""通过指定输出的文件或格式,以获取对应的导出全曲用插件"""
|
||||
return self._get_io_plugin_by_format(
|
||||
self._music_output_plugins, filepath_or_format
|
||||
)
|
||||
|
||||
def get_track_output_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[TrackOutputPluginBase, None, None]:
|
||||
"""通过指定输出的文件或格式,以获取对应的导出单个音轨用插件"""
|
||||
return self._get_io_plugin_by_format(
|
||||
self._track_output_plugins, filepath_or_format
|
||||
)
|
||||
|
||||
def _get_plugin_by_name(
|
||||
self,
|
||||
plugin_regdict: Mapping[str, T_Plugin],
|
||||
plugin_name: str,
|
||||
plugin_usage: str = "",
|
||||
) -> T_Plugin:
|
||||
"""通过指定名称,以获取对应的插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in plugin_regdict.values()
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于{}、名为`{}`”的插件".format(plugin_usage, plugin_name)
|
||||
)
|
||||
|
||||
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPluginBase:
|
||||
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
|
||||
return self._get_plugin_by_name(
|
||||
self._music_input_plugins, plugin_name, "导入全曲"
|
||||
)
|
||||
|
||||
def get_track_input_plugin(self, plugin_name: str) -> TrackInputPluginBase:
|
||||
"""获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的"""
|
||||
return self._get_plugin_by_name(
|
||||
self._track_input_plugins, plugin_name, "导入单轨"
|
||||
)
|
||||
|
||||
def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePluginBase:
|
||||
"""获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的"""
|
||||
return self._get_plugin_by_name(
|
||||
self._music_operate_plugins, plugin_name, "处理整个曲目"
|
||||
)
|
||||
|
||||
def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePluginBase:
|
||||
"""获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的"""
|
||||
return self._get_plugin_by_name(
|
||||
self._track_operate_plugins, plugin_name, "处理单个音轨"
|
||||
)
|
||||
|
||||
def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPluginBase:
|
||||
"""获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的"""
|
||||
return self._get_plugin_by_name(
|
||||
self._music_output_plugins, plugin_name, "导出完整曲目"
|
||||
)
|
||||
|
||||
def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPluginBase:
|
||||
"""获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的"""
|
||||
return self._get_plugin_by_name(
|
||||
self._track_output_plugins, plugin_name, "导出单个音轨"
|
||||
)
|
||||
|
||||
def get_service_plugin(self, plugin_name: str) -> ServicePluginBase:
|
||||
"""获取服务用插件,当名称重叠时,取版本号最大的"""
|
||||
return self._get_plugin_by_name(self._service_plugins, plugin_name, "提供服务")
|
||||
|
||||
def get_library_plugin(self, plugin_name: str) -> LibraryPluginBase:
|
||||
"""获取依赖库类插件,当名称重叠时,取版本号最高的"""
|
||||
return self._get_plugin_by_name(
|
||||
self._library_plugins, plugin_name, "作为依赖库"
|
||||
)
|
||||
|
||||
def supported_input_formats(self) -> Set[str]:
|
||||
"""所有支持的导入格式"""
|
||||
return set(
|
||||
chain.from_iterable(
|
||||
plugin.supported_formats
|
||||
for plugin in chain(
|
||||
self._music_input_plugins.values(),
|
||||
self._track_input_plugins.values(),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def supported_output_formats(self) -> Set[str]:
|
||||
"""所有支持的导出格式"""
|
||||
return set(
|
||||
chain.from_iterable(
|
||||
plugin.supported_formats
|
||||
for plugin in chain(
|
||||
self._music_output_plugins.values(),
|
||||
self._track_output_plugins.values(),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
_global_plugin_registry = PluginRegistry()
|
||||
"""全局插件注册表实例"""
|
||||
|
||||
|
||||
def __plugin_regist_decorator(plg_id: str, rgst_func: Callable[[type, str], None]):
|
||||
|
||||
def decorator(cls):
|
||||
global _global_plugin_registry
|
||||
cls.id = plg_id
|
||||
rgst_func(cls, plg_id)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def music_input_plugin(plugin_id: str):
|
||||
"""全曲输入用插件装饰器"""
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_music_input_plugin
|
||||
)
|
||||
|
||||
|
||||
def track_input_plugin(plugin_id: str):
|
||||
"""单轨输入用插件装饰器"""
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_track_input_plugin
|
||||
)
|
||||
|
||||
|
||||
def music_operate_plugin(plugin_id: str):
|
||||
"""全曲处理用插件装饰器"""
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_music_operate_plugin
|
||||
)
|
||||
|
||||
def track_operate_plugin(plugin_id: str):
|
||||
"""音轨处理插件装饰器"""
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_track_operate_plugin
|
||||
)
|
||||
|
||||
|
||||
def music_output_plugin(plugin_id: str):
|
||||
"""乐曲输出用插件装饰器"""
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_music_output_plugin
|
||||
)
|
||||
|
||||
|
||||
def track_output_plugin(plugin_id: str):
|
||||
"""音轨输出用插件装饰器"""
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_track_output_plugin
|
||||
)
|
||||
|
||||
|
||||
def service_plugin(plugin_id: str):
|
||||
"""服务插件装饰器"""
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_service_plugin
|
||||
)
|
||||
|
||||
def library_plugin(plugin_id: str):
|
||||
"""支持库插件装饰器"""
|
||||
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_library_plugin
|
||||
)
|
||||
@@ -1,483 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
旧版本转换功能以及已经弃用的函数
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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 *
|
||||
from .constants 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 = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
else:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
|
||||
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)
|
||||
if msg.channel == 9:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
else:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
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 = (
|
||||
inst_to_sould_with_deviation(
|
||||
msg[1], MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
if SpecialBits
|
||||
else inst_to_sould_with_deviation(
|
||||
InstID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
)
|
||||
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)
|
||||
)
|
||||
|
||||
if msg.channel == 9:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
else:
|
||||
soundID, _X = inst_to_sould_with_deviation(
|
||||
instrumentID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
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 = (
|
||||
inst_to_sould_with_deviation(
|
||||
msg[1], MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE
|
||||
)
|
||||
if SpecialBits
|
||||
else inst_to_sould_with_deviation(
|
||||
InstID, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE
|
||||
)
|
||||
)
|
||||
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)]
|
||||
@@ -1,401 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储许多非主要的相关类
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from .types import Optional, Any, List, Mapping
|
||||
|
||||
from .constants import MC_PERCUSSION_INSTRUMENT_LIST
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class SingleNote:
|
||||
"""存储单个音符的类"""
|
||||
|
||||
instrument: int
|
||||
"""乐器编号"""
|
||||
|
||||
note: int
|
||||
"""音符编号"""
|
||||
|
||||
velocity: int
|
||||
"""力度/响度"""
|
||||
|
||||
start_time: int
|
||||
"""开始之时 ms"""
|
||||
|
||||
duration: int
|
||||
"""音符持续时间 ms"""
|
||||
|
||||
track_no: int
|
||||
"""音符所处的音轨"""
|
||||
|
||||
percussive: bool
|
||||
"""是否为打击乐器"""
|
||||
|
||||
extra_info: Any
|
||||
"""你觉得放什么好?"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
instrument: int,
|
||||
pitch: int,
|
||||
velocity: int,
|
||||
startime: int,
|
||||
lastime: int,
|
||||
track_number: int = 0,
|
||||
is_percussion: Optional[bool] = None,
|
||||
extra_information: Any = None,
|
||||
):
|
||||
"""用于存储单个音符的类
|
||||
: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
|
||||
"""音符所处的音轨"""
|
||||
|
||||
self.percussive = (
|
||||
(is_percussion in MC_PERCUSSION_INSTRUMENT_LIST)
|
||||
if (is_percussion is None)
|
||||
else is_percussion
|
||||
)
|
||||
"""是否为打击乐器"""
|
||||
|
||||
self.extra_info = extra_information
|
||||
|
||||
@property
|
||||
def inst(self) -> int:
|
||||
"""乐器编号"""
|
||||
return self.instrument
|
||||
|
||||
@inst.setter
|
||||
def inst(self, inst_: int):
|
||||
self.instrument = inst_
|
||||
|
||||
@property
|
||||
def pitch(self) -> int:
|
||||
"""音符编号"""
|
||||
return self.note
|
||||
|
||||
# @property
|
||||
# def get_mc_pitch(self,table: Dict[int, Tuple[str, int]]) -> float:
|
||||
# self.mc_sound_ID, _X = inst_to_sould_with_deviation(self.inst,table,"note.bd" if self.percussive else "note.flute",)
|
||||
# return -1 if self.percussive else 2 ** ((self.note - 60 - _X) / 12)
|
||||
|
||||
def set_info(self, sth: Any):
|
||||
"""设置附加信息"""
|
||||
self.extra_info = sth
|
||||
|
||||
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 "",
|
||||
)
|
||||
|
||||
def __tuple__(self):
|
||||
return (
|
||||
(
|
||||
self.percussive,
|
||||
self.inst,
|
||||
self.velocity,
|
||||
self.start_time,
|
||||
self.duration,
|
||||
self.track_no,
|
||||
)
|
||||
if self.percussive
|
||||
else (
|
||||
self.percussive,
|
||||
self.inst,
|
||||
self.note,
|
||||
self.velocity,
|
||||
self.start_time,
|
||||
self.duration,
|
||||
self.track_no,
|
||||
)
|
||||
)
|
||||
|
||||
def __dict__(self):
|
||||
return (
|
||||
{
|
||||
"Percussive": self.percussive,
|
||||
"Instrument": self.inst,
|
||||
"Velocity": self.velocity,
|
||||
"StartTime": self.start_time,
|
||||
"Duration": self.duration,
|
||||
"Track": self.track_no,
|
||||
}
|
||||
if self.percussive
|
||||
else {
|
||||
"Percussive": self.percussive,
|
||||
"Instrument": self.inst,
|
||||
"Pitch": self.note,
|
||||
"Velocity": self.velocity,
|
||||
"StartTime": self.start_time,
|
||||
"Duration": self.duration,
|
||||
"Track": self.track_no,
|
||||
}
|
||||
)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
return self.__str__() == other.__str__()
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class SingleCommand:
|
||||
"""存储单个指令的类"""
|
||||
|
||||
command_text: str
|
||||
"""指令文本"""
|
||||
|
||||
conditional: bool
|
||||
"""执行是否有条件"""
|
||||
|
||||
delay: int
|
||||
"""执行的延迟"""
|
||||
|
||||
annotation_text: str
|
||||
"""指令注释"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
command: str,
|
||||
condition: bool = False,
|
||||
tick_delay: int = 0,
|
||||
annotation: str = "",
|
||||
):
|
||||
"""
|
||||
存储单个指令的类
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command: str
|
||||
指令
|
||||
condition: bool
|
||||
是否有条件
|
||||
tick_delay: int
|
||||
执行延时
|
||||
annotation: str
|
||||
注释
|
||||
"""
|
||||
self.command_text = command
|
||||
self.conditional = condition
|
||||
self.delay = tick_delay
|
||||
self.annotation_text = annotation
|
||||
|
||||
def copy(self):
|
||||
return SingleCommand(
|
||||
command=self.command_text,
|
||||
condition=self.conditional,
|
||||
tick_delay=self.delay,
|
||||
annotation=self.annotation_text,
|
||||
)
|
||||
|
||||
@property
|
||||
def cmd(self) -> str:
|
||||
"""
|
||||
我的世界函数字符串(包含注释)
|
||||
"""
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
转为我的世界函数文件格式(包含注释)
|
||||
"""
|
||||
return "#[{cdt}]<{delay}> {ant}\n{cmd}".format(
|
||||
cdt="CDT" if self.conditional else "",
|
||||
delay=self.delay,
|
||||
ant=self.annotation_text,
|
||||
cmd=self.command_text,
|
||||
)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
return self.__str__() == other.__str__()
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class SingleNoteBox:
|
||||
"""存储单个音符盒"""
|
||||
|
||||
instrument_block: str
|
||||
"""乐器方块"""
|
||||
|
||||
note_value: int
|
||||
"""音符盒音高"""
|
||||
|
||||
annotation_text: str
|
||||
"""音符注释"""
|
||||
|
||||
is_percussion: bool
|
||||
"""是否为打击乐器"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
instrument_block_: str,
|
||||
note_value_: int,
|
||||
percussion: Optional[bool] = None,
|
||||
annotation: str = "",
|
||||
):
|
||||
"""用于存储单个音符盒的类
|
||||
:param instrument_block_ 音符盒演奏所使用的乐器方块
|
||||
:param note_value_ 音符盒的演奏音高
|
||||
:param percussion 此音符盒乐器是否作为打击乐处理
|
||||
注:若为空,则自动识别是否为打击乐器
|
||||
:param annotation 音符注释"""
|
||||
self.instrument_block = instrument_block_
|
||||
"""乐器方块"""
|
||||
self.note_value = note_value_
|
||||
"""音符盒音高"""
|
||||
self.annotation_text = annotation
|
||||
"""音符注释"""
|
||||
if percussion is None:
|
||||
self.is_percussion = percussion in MC_PERCUSSION_INSTRUMENT_LIST
|
||||
else:
|
||||
self.is_percussion = percussion
|
||||
|
||||
@property
|
||||
def inst(self) -> str:
|
||||
"""获取音符盒下的乐器方块"""
|
||||
return self.instrument_block
|
||||
|
||||
@inst.setter
|
||||
def inst(self, inst_):
|
||||
self.instrument_block = inst_
|
||||
|
||||
@property
|
||||
def note(self) -> int:
|
||||
"""获取音符盒音调特殊值"""
|
||||
return self.note_value
|
||||
|
||||
@note.setter
|
||||
def note(self, note_):
|
||||
self.note_value = note_
|
||||
|
||||
@property
|
||||
def annotation(self) -> str:
|
||||
"""获取音符盒的备注"""
|
||||
return self.annotation_text
|
||||
|
||||
@annotation.setter
|
||||
def annotation(self, annotation_):
|
||||
self.annotation_text = annotation_
|
||||
|
||||
def copy(self):
|
||||
return SingleNoteBox(
|
||||
instrument_block_=self.instrument_block,
|
||||
note_value_=self.note_value,
|
||||
annotation=self.annotation_text,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Note(inst = {self.inst}, note = {self.note}, )"
|
||||
|
||||
def __tuple__(self) -> tuple:
|
||||
return self.inst, self.note, self.annotation
|
||||
|
||||
def __dict__(self) -> dict:
|
||||
return {
|
||||
"inst": self.inst,
|
||||
"note": self.note,
|
||||
"annotation": self.annotation,
|
||||
}
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
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: str, to_play_s: str, played_s: str):
|
||||
"""用于存储进度条样式的类
|
||||
:param base_s 基础样式,用以定义进度条整体
|
||||
:param to_play_s 进度条样式:尚未播放的样子
|
||||
:param played_s 已经播放的样子"""
|
||||
self.base_style = base_s
|
||||
self.to_play_style = to_play_s
|
||||
self.played_style = played_s
|
||||
|
||||
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
|
||||
|
||||
|
||||
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
|
||||
r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]",
|
||||
r"§e=§r",
|
||||
r"§7=§r",
|
||||
)
|
||||
"""
|
||||
默认的进度条样式
|
||||
"""
|
||||
|
||||
NoteChannelType = Mapping[
|
||||
int,
|
||||
List[SingleNote,],
|
||||
]
|
||||
"""
|
||||
频道信息类型
|
||||
|
||||
Dict[int,Dict[int,List[SingleNote,],],]
|
||||
"""
|
||||
@@ -1,12 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存放数据类型的定义
|
||||
存储 音·创 v3 定义的一些数据类型,可以用于类型检查器
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||
Copyright © 2026 Eilles & YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -16,49 +16,10 @@ 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,
|
||||
Iterable,
|
||||
Sequence,
|
||||
Mapping,
|
||||
Callable,
|
||||
)
|
||||
|
||||
|
||||
MidiNoteNameTableType = Mapping[int, Tuple[str, ...]]
|
||||
"""
|
||||
Midi音符名称对照表类型
|
||||
"""
|
||||
|
||||
MidiInstrumentTableType = Mapping[int, Tuple[str, int]]
|
||||
"""
|
||||
Midi乐器对照表类型
|
||||
"""
|
||||
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
|
||||
|
||||
FittingFunctionType = Callable[[float], float]
|
||||
|
||||
|
||||
ChannelType = Dict[
|
||||
int,
|
||||
Dict[
|
||||
int,
|
||||
List[
|
||||
Union[
|
||||
Tuple[Literal["PgmC"], int, int],
|
||||
Tuple[Literal["NoteS"], int, int, int],
|
||||
Tuple[Literal["NoteE"], int, int],
|
||||
]
|
||||
],
|
||||
],
|
||||
]
|
||||
"""
|
||||
以字典所标记的频道信息类型(已弃用)
|
||||
|
||||
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
|
||||
拟合函数类型
|
||||
"""
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放主程序所必须的功能性内容
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
import math
|
||||
import random
|
||||
|
||||
from .constants import MM_INSTRUMENT_DEVIATION_TABLE, MC_INSTRUMENT_BLOCKS_TABLE
|
||||
from .subclass import SingleNote
|
||||
|
||||
from .types import (
|
||||
Any,
|
||||
Dict,
|
||||
Tuple,
|
||||
Optional,
|
||||
Callable,
|
||||
Literal,
|
||||
Union,
|
||||
MidiInstrumentTableType,
|
||||
)
|
||||
|
||||
|
||||
def mctick2timestr(mc_tick: int) -> str:
|
||||
"""
|
||||
将《我的世界》的游戏刻计转为表示时间的字符串
|
||||
"""
|
||||
return str(int(int(mc_tick / 20) / 60)) + ":" + str(int(int(mc_tick / 20) % 60))
|
||||
|
||||
|
||||
def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]:
|
||||
"""
|
||||
空MIDI通道字典
|
||||
"""
|
||||
|
||||
return dict(
|
||||
(
|
||||
i,
|
||||
(staff.copy() if isinstance(staff, (dict, list)) else staff),
|
||||
) # 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
||||
for i in range(channel_count)
|
||||
)
|
||||
|
||||
|
||||
def inst_to_sould_with_deviation(
|
||||
instrumentID: int,
|
||||
reference_table: MidiInstrumentTableType,
|
||||
default_instrument: str = "note.flute",
|
||||
default_deviation: Optional[int] = 5,
|
||||
) -> Tuple[str, int]:
|
||||
"""
|
||||
返回midi的乐器ID对应的我的世界乐器名,对于音域转换算法,如下:
|
||||
2**( ( msg.note - 60 - X ) / 12 ) 即为MC的音高,其中
|
||||
X的取值随乐器不同而变化:
|
||||
竖琴harp、电钢琴pling、班卓琴banjo、方波bit、颤音琴iron_xylophone 的时候为6
|
||||
吉他的时候为7
|
||||
贝斯bass、迪吉里杜管didgeridoo的时候为8
|
||||
长笛flute、牛铃cou_bell的时候为5
|
||||
钟琴bell、管钟chime、木琴xylophone的时候为4
|
||||
而存在一些打击乐器bd(basedrum)、hat、snare,没有音域,则没有X,那么我们返回7即可
|
||||
|
||||
Parameters
|
||||
----------
|
||||
instrumentID: int
|
||||
midi的乐器ID
|
||||
reference_table: Dict[int, Tuple[str, int]]
|
||||
转换乐器参照表
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple(str我的世界乐器名, int转换算法中的X)
|
||||
"""
|
||||
return reference_table.get(
|
||||
instrumentID,
|
||||
(
|
||||
default_instrument,
|
||||
(
|
||||
default_deviation
|
||||
if default_deviation
|
||||
else MM_INSTRUMENT_DEVIATION_TABLE.get(default_instrument, -1)
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# 明明已经走了
|
||||
# 凭什么还要在我心里留下缠绵缱绻
|
||||
|
||||
|
||||
def natural_curve(
|
||||
vol: float,
|
||||
) -> float:
|
||||
"""
|
||||
midi力度值拟合成的距离函数
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vol: int
|
||||
midi音符力度值
|
||||
|
||||
Returns
|
||||
-------
|
||||
float播放中心到玩家的距离
|
||||
"""
|
||||
return (
|
||||
-8.081720684086314
|
||||
* math.log(
|
||||
vol + 14.579508825070013,
|
||||
)
|
||||
+ 37.65806375944386
|
||||
if vol < 60.64
|
||||
else 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649)
|
||||
+ -6.313841334963396 * (vol + 2592.272889454798)
|
||||
+ 4558.496367823575
|
||||
)
|
||||
|
||||
|
||||
def straight_line(vol: float) -> float:
|
||||
"""
|
||||
midi力度值拟合成的距离函数
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vol: int
|
||||
midi音符力度值
|
||||
|
||||
Returns
|
||||
-------
|
||||
float播放中心到玩家的距离
|
||||
"""
|
||||
return vol / -8 + 16
|
||||
|
||||
|
||||
def note_to_command_parameters(
|
||||
note_: SingleNote,
|
||||
reference_table: MidiInstrumentTableType,
|
||||
volume_percentage: float = 1,
|
||||
volume_processing_method: Callable[[float], float] = natural_curve,
|
||||
) -> Tuple[
|
||||
str,
|
||||
float,
|
||||
float,
|
||||
Union[float, Literal[None]],
|
||||
]:
|
||||
"""
|
||||
将音符转为播放的指令
|
||||
:param note_:int 音符对象
|
||||
:param reference_table:Dict[int, Tuple[str, int]] 转换对照表
|
||||
:param volume_percentage:int 音量占比(0,1]
|
||||
:param volume_proccessing_method:Callable[[float], float]: 音量处理函数
|
||||
|
||||
:return str[我的世界音符ID], float[播放距离], float[指令音量参数], float[指令音调参数]
|
||||
"""
|
||||
mc_sound_ID, deviation = inst_to_sould_with_deviation(
|
||||
note_.inst,
|
||||
reference_table,
|
||||
"note.bd" if note_.percussive else "note.flute",
|
||||
)
|
||||
|
||||
# delaytime_now = round(self.start_time / float(speed) / 50)
|
||||
mc_pitch = None if note_.percussive else 2 ** ((note_.note - 60 - deviation) / 12)
|
||||
|
||||
mc_distance_volume = volume_processing_method(note_.velocity * volume_percentage)
|
||||
|
||||
return mc_sound_ID, mc_distance_volume, volume_percentage, mc_pitch
|
||||
|
||||
|
||||
def from_single_note(
|
||||
note_: SingleNote, random_select: bool = False, default_block: str = "air"
|
||||
):
|
||||
"""
|
||||
将我的世界乐器名改作音符盒所需的对应方块名称
|
||||
|
||||
Parameters
|
||||
----------
|
||||
note_: SingleNote
|
||||
音符类
|
||||
random_select: bool
|
||||
是否随机选取对应方块
|
||||
default_block: str
|
||||
查表查不到怎么办?默认一个!
|
||||
|
||||
Returns
|
||||
-------
|
||||
str方块名称
|
||||
"""
|
||||
pass
|
||||
# return SingleNoteBox() # TO-DO
|
||||
|
||||
|
||||
@staticmethod
|
||||
def soundID_to_blockID(
|
||||
sound_id: str, random_select: bool = False, default_block: str = "air"
|
||||
) -> str:
|
||||
"""
|
||||
将我的世界乐器名改作音符盒所需的对应方块名称
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sound_id: str
|
||||
将我的世界乐器名
|
||||
random_select: bool
|
||||
是否随机选取对应方块
|
||||
default_block: str
|
||||
查表查不到怎么办?默认一个!
|
||||
|
||||
Returns
|
||||
-------
|
||||
str方块名称
|
||||
"""
|
||||
if random_select:
|
||||
return random.choice(MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,)))
|
||||
else:
|
||||
return MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,))[0]
|
||||
101
README.md
101
README.md
@@ -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: 玉衡Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-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">
|
||||
@@ -18,7 +23,7 @@
|
||||
<p>
|
||||
|
||||
[![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
|
||||
[![][Bilibili: 诸葛亮与八卦阵]](https://space.bilibili.com/604072474)
|
||||
[![][Bilibili: 玉衡Alioth]](https://space.bilibili.com/604072474)
|
||||
[![CodeStyle: black]](https://github.com/psf/black)
|
||||
[![][python]](https://www.python.org/)
|
||||
[![][license]](LICENSE)
|
||||
@@ -29,33 +34,42 @@
|
||||
[](https://github.com/TriM-Organization/Musicreater/stargazers)
|
||||
[](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**:我的世界基岩版玩家,喜欢编程和音乐,深圳初二学生。
|
||||
**玉衡Alioth Alioth**:我的世界基岩版玩家,喜欢编程和音乐,学生。
|
||||
|
||||
**偷吃不是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 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
|
||||
- 感谢 **[神羽 “SnowyKami”](https://www.sfkm.me/)** 对我们项目的支持与宣传,非常感谢他为我们提供的服务器!
|
||||
- 感谢 **指令师\_苦力怕 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>
|
||||
- 感谢 **雨**\<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
|
||||
|
||||
98
README_EN.md
98
README_EN.md
@@ -1,3 +1,10 @@
|
||||
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFEilles-00A1E7?style=for-the-badge
|
||||
[Bilibili: Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-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 and open-source library for handling with <i>Minecraft</i> digital music.</h3>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
||||
@@ -15,7 +22,7 @@
|
||||
<p>
|
||||
|
||||
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
|
||||
[![][Bilibili: bgArray]](https://space.bilibili.com/604072474)
|
||||
[![][Bilibili: Alioth]](https://space.bilibili.com/604072474)
|
||||
[![CodeStyle: black]](https://github.com/psf/black)
|
||||
[![][python]](https://www.python.org/)
|
||||
[![][license]](LICENSE)
|
||||
@@ -28,37 +35,44 @@
|
||||
|
||||
[简体中文 🇨🇳](README.md) | English🇬🇧
|
||||
|
||||
**Notice that the localizations of documents may NOT be up-to-date.**
|
||||
**Notice that the localizations of documents may probably NOT be up-to-date. The original document is in Chinese.**
|
||||
|
||||
## 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 handling with digital musics that being able to be 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 (Latest but **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.
|
||||
**Alioth (玉衡Alioth)**: 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
|
||||
|
||||
## 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=""It was once, a convert-QQ-bot is just in front my eyes" "Until lose, I finally know cannot chase back what I needs""></td><td><small>"It was once, a convert-QQ-bot is just in front my eyes"<br>"Until lose, I finally know cannot chase back what I needs"</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> <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=""It was once, a convert-QQ-bot is just in front my eyes" "Until lose, I finally know cannot chase back what I needs""></td><td><small>"It was once, a convert-QQ-bot is just in front my eyes"<br>"Until lose, I finally know cannot chase back what I needs"</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
|
||||
>
|
||||
> 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
|
||||
- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易璀璨网络科技有限公司
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
48
TO-DO.md
Normal file
48
TO-DO.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 任务清单
|
||||
|
||||
## 待办事项
|
||||
- 乐曲文件格式设计
|
||||
目前想到的是:
|
||||
1. 使用 `.MCT` 作为项目文件的后缀,然后考虑一下格式是否和之前的 MusicSequence 兼容,如果兼容的话可以照旧用 `.MSQ`,如不的话,可以试试想一个新的后缀名作为数据文件后缀
|
||||
2. 要求数据文件支持完全流式读入
|
||||
|
||||
- 音轨静音处理
|
||||
当前没有处理
|
||||
|
||||
- 优化音轨的存储方式
|
||||
当前是用列表,且每一次变动元素都要重新排序,这样消耗太大了,需要优化,改用最小堆形式(heapq)
|
||||
|
||||
- 移植 v2 功能到内置插件
|
||||
目前 v2 的功能有很多,都要移植到 v3。
|
||||
1. 导入 Midi 文件到全曲
|
||||
2. 导入 Midi 文件到指定轨道
|
||||
3. 导出到延迟播放器的结构文件(MCSTRUCTURE、BDX)
|
||||
4. 导出到延迟播放器的附加包
|
||||
5. 导出到积分板播放器的以上两种形式
|
||||
6. 导出到中继器播放器的以上两种形式
|
||||
7. 在 WebSocket 播放器中播放
|
||||
8. 导出到支持神羽资源包的以上 7 种形式
|
||||
9. 对于 Midi 歌词的实验性功能
|
||||
10. 对于 Java 版本适配的实验性功能
|
||||
11. 对于听感优化的实验性功能(插值、偏移)
|
||||
|
||||
- 测试参数曲线的功能
|
||||
|
||||
- 支持导出音符盒构成的音乐
|
||||
|
||||
- 支持导出成 schematic 结构
|
||||
|
||||
## 讨论
|
||||
|
||||
1. [x] 是否应该在插件注册表 PluginRegistry 中采用 `Dict[插件名, 插件对象]` 的形式存储插件?
|
||||
当前不采用这种方式是认为可以兼容一些极端场景下用户将一堆同名插件放在一起的情况。但是就算是插件放在一起,我们也可以有选择地读入注册表,比如依照版本号只读取最高版本的插件,并不需要全部存储在插件注册表中。所以其实用字典来存储是有利的?吗?
|
||||
|
||||
**当前已解决**
|
||||
|
||||
引入了插件惟一识别码之后当然是采用 `Dict[插件唯一识别码, 插件对象]` 来存储插件了~之前插件名称的内容是我想得太浅了,我写完所有代码之后才想到插件名称是中文还带空格的任意字符串……
|
||||
|
||||
2. 服务插件到底该怎么写?总不能留着一个 PluginType.SERVICE 的插件一直空在那里吧……
|
||||
|
||||
3. 插件依赖性的优化。目前没有处理各个插件依赖关系的问题,如果插件之间彼此依赖要怎么做?
|
||||
我的想法是,这个依赖的处理由调用端来完成。比如我们的 伶伦工作站 是以 音·创 为核心的一个可视化数字音频工作站。
|
||||
那么应该由伶伦来处理依赖关系并加载之。
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
**此为开发相关文档,内容包括:库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释**
|
||||
|
||||
@@ -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>
|
||||
|
||||
**此为开发相关文档,内容包括:库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释**
|
||||
|
||||
@@ -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>
|
||||
|
||||
# 生成文件的使用
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
**_使用时请遵循协议规定_**
|
||||
|
||||
- 版权所有 © 2024 音·创 开发者
|
||||
- Copyright © 2024 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\
|
||||
|
||||
149
docs/音乐序列文件格式.md
Normal file
149
docs/音乐序列文件格式.md
Normal 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 位 | 若前述**是否启用声像位移**已启用,则此值启用;三个值分别代表 x、y、z 轴上的偏移,每个值支持数值 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 校验值,以 全曲音符总数 作为种子值。
|
||||
@@ -1,13 +0,0 @@
|
||||
import Musicreater.experiment
|
||||
import Musicreater.plugin
|
||||
import Musicreater.plugin.mcstructfile
|
||||
|
||||
print(
|
||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
|
||||
input("midi路径:"), old_exe_format=False
|
||||
),
|
||||
Musicreater.plugin.ConvertConfig(input("输出路径:"), volume=1),
|
||||
max_height=32,
|
||||
)
|
||||
)
|
||||
1254
old-things/Musicreater/experiment.py
Normal file
1254
old-things/Musicreater/experiment.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@
|
||||
|
||||
# 音·创 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
|
||||
# 版权所有 金羿("Eilles") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
Musicreater (音·创)
|
||||
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
|
||||
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 ../License.md
|
||||
Terms & Conditions: ../License.md
|
||||
@@ -183,6 +183,7 @@ if __name__ == "__main__":
|
||||
|
||||
# ============================
|
||||
from typing import Literal
|
||||
|
||||
from ..constants import x, y, z
|
||||
|
||||
|
||||
@@ -198,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), # 声明结构大小
|
||||
165
old-things/Musicreater/old_exceptions.py
Normal file
165
old-things/Musicreater/old_exceptions.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存放一些报错类型
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
class MSCTBaseException(Exception):
|
||||
"""音·创 的所有错误均继承于此"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音·创 的所有错误均继承于此"""
|
||||
super().__init__("音·创", *args)
|
||||
|
||||
def meow(
|
||||
self,
|
||||
):
|
||||
for i in self.args:
|
||||
print(i + "喵!")
|
||||
|
||||
def crash_it(self):
|
||||
raise self
|
||||
|
||||
|
||||
class MidiFormatException(MSCTBaseException):
|
||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||
super().__init__("MIDI 格式错误", *args)
|
||||
|
||||
|
||||
class MidiDestroyedError(MSCTBaseException):
|
||||
"""Midi文件损坏"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Midi文件损坏"""
|
||||
super().__init__("MIDI文件损坏:无法读取 MIDI 文件", *args)
|
||||
|
||||
|
||||
# class MidiUnboundError(MSCTBaseException):
|
||||
# """未定义Midi对象(无用)"""
|
||||
|
||||
# def __init__(self, *args):
|
||||
# """未绑定Midi对象"""
|
||||
# super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args)
|
||||
# 此错误在本版本内已经不再使用
|
||||
|
||||
|
||||
class CommandFormatError(MSCTBaseException, RuntimeError):
|
||||
"""指令格式与目标格式不匹配而引起的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""指令格式与目标格式不匹配而引起的错误"""
|
||||
super().__init__("指令格式不匹配", *args)
|
||||
|
||||
|
||||
# class CrossNoteError(MidiFormatException):
|
||||
# """同通道下同音符交叉出现所产生的错误"""
|
||||
|
||||
# def __init__(self, *args):
|
||||
# """同通道下同音符交叉出现所产生的错误"""
|
||||
# super().__init__("同通道下同音符交叉", *args)
|
||||
# 这TM是什么错误?
|
||||
# 我什么时候写的这玩意?
|
||||
# 我哪知道这说的是啥?
|
||||
# !!!
|
||||
# 我知道这是什么了 —— 金羿 2025 0401
|
||||
# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息
|
||||
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
||||
|
||||
|
||||
class NotDefineTempoError(MidiFormatException):
|
||||
"""没有Tempo设定导致时间无法计算的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""没有Tempo设定导致时间无法计算的错误"""
|
||||
super().__init__("在曲目开始时没有声明 Tempo(未指定拍长)", *args)
|
||||
|
||||
|
||||
class ChannelOverFlowError(MidiFormatException):
|
||||
"""一个midi中含有过多的通道"""
|
||||
|
||||
def __init__(self, max_channel=16, *args):
|
||||
"""一个midi中含有过多的通道"""
|
||||
super().__init__("含有过多的通道(数量应≤{})".format(max_channel), *args)
|
||||
|
||||
|
||||
class NotDefineProgramError(MidiFormatException):
|
||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||
super().__init__("未指定演奏乐器", *args)
|
||||
|
||||
|
||||
class NoteOnOffMismatchError(MidiFormatException):
|
||||
"""音符开音和停止不匹配的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音符开音和停止不匹配的错误"""
|
||||
super().__init__("音符不匹配", *args)
|
||||
|
||||
|
||||
class LyricMismatchError(MSCTBaseException):
|
||||
"""歌词匹配解析错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""有可能产生了错误的歌词解析"""
|
||||
super().__init__("歌词解析错误", *args)
|
||||
|
||||
# 已重构
|
||||
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
|
||||
"""以0作为播放速度的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""以0作为播放速度的错误"""
|
||||
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)
|
||||
144
old-things/Musicreater/old_init.py
Normal file
144
old-things/Musicreater/old_init.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""一个简单的我的世界音频转换库
|
||||
音·创 (Musicreater)
|
||||
是一款免费开源的《我的世界》数字音频支持库。
|
||||
Musicreater(音·创)
|
||||
A free open source library used for dealing with **Minecraft** digital musics.
|
||||
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
|
||||
The Licensor of Musicreater("this project") is Eilles, bgArray.
|
||||
|
||||
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||
任何人皆可从以下地址获得本协议副本:https://gitee.com/EillesWan/YulvLicenses。
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||
详细的准许和限制条款请见原协议文本。
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__version__ = "2.4.2.3"
|
||||
__vername__ = "音符附加信息升级"
|
||||
__author__ = (
|
||||
("金羿", "Eilles"),
|
||||
("诸葛亮与八卦阵", "bgArray"),
|
||||
("鱼旧梦", "ElapsingDreams"),
|
||||
("偷吃不是Touch", "Touch"),
|
||||
)
|
||||
__all__ = [
|
||||
# 主要类
|
||||
"MusicSequence",
|
||||
"MidiConvert",
|
||||
# 附加类
|
||||
# "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 .old_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,
|
||||
)
|
||||
1816
old-things/Musicreater/old_main.py
Normal file
1816
old-things/Musicreater/old_main.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放非音·创本体的附加内容(插件?)
|
||||
存放非音·创本体的附加功能件
|
||||
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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__ = [
|
||||
# 通用
|
||||
"ConvertConfig",
|
||||
# "ConvertConfig",
|
||||
"bottem_side_length_of_smallest_square_bottom_box",
|
||||
# 打包
|
||||
"compress_zipfile",
|
||||
@@ -34,6 +34,7 @@ __all__ = [
|
||||
"AXIS_PARTICULAR_VALUE",
|
||||
"COMPABILITY_VERSION_117",
|
||||
"COMPABILITY_VERSION_119",
|
||||
"COMPABILITY_VERSION_121",
|
||||
# BDX 函数
|
||||
"bdx_move",
|
||||
"form_command_block_in_BDX_bytes",
|
||||
@@ -41,9 +42,9 @@ __all__ = [
|
||||
# BDX 常量
|
||||
"BDX_MOVE_KEY",
|
||||
]
|
||||
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"))
|
||||
__author__ = (("金羿", "Eilles"), ("诸葛亮与八卦阵", "bgArray"))
|
||||
|
||||
from .main import ConvertConfig
|
||||
# from .main import ConvertConfig
|
||||
|
||||
from .archive import compress_zipfile, behavior_mcpack_manifest
|
||||
|
||||
@@ -62,6 +63,7 @@ from .mcstructure import (
|
||||
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,
|
||||
@@ -2,8 +2,8 @@
|
||||
"""
|
||||
用以生成附加包的附加功能
|
||||
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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,
|
||||
)
|
||||
684
old-things/Musicreater/old_plugin/addonpack/main.py
Normal file
684
old-things/Musicreater/old_plugin/addonpack/main.py
Normal file
@@ -0,0 +1,684 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from typing import Literal, Optional, Tuple
|
||||
|
||||
from ...old_main import MidiConvert
|
||||
from ...subclass import ProgressBarStyle
|
||||
from ..archive import behavior_mcpack_manifest, compress_zipfile
|
||||
from ..mcstructure import (
|
||||
COMPABILITY_VERSION_117,
|
||||
COMPABILITY_VERSION_119,
|
||||
Structure,
|
||||
commands_to_redstone_delay_structure,
|
||||
commands_to_structure,
|
||||
form_command_block_in_NBT_struct,
|
||||
)
|
||||
|
||||
|
||||
def to_addon_pack_in_score(
|
||||
midi_cvt: MidiConvert,
|
||||
dist_path: str,
|
||||
progressbar_style: Optional[ProgressBarStyle],
|
||||
scoreboard_name: str = "mscplay",
|
||||
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_score(
|
||||
scoreboard_name=scoreboard_name,
|
||||
)
|
||||
|
||||
# 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目,然后创建
|
||||
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"{dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
|
||||
json.dump(
|
||||
behavior_mcpack_manifest(
|
||||
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,
|
||||
)
|
||||
|
||||
# 写入stop.mcfunction
|
||||
with open(
|
||||
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"{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"{dist_path}/temp/functions/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[scores={"
|
||||
+ scoreboard_name
|
||||
+ "=1..}] "
|
||||
+ 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 progressbar_style else "",
|
||||
)
|
||||
)
|
||||
|
||||
if progressbar_style:
|
||||
with open(
|
||||
f"{dist_path}/temp/functions/mscplay/progressShow.mcfunction",
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
f.writelines(
|
||||
"\n".join(
|
||||
[
|
||||
single_cmd.cmd
|
||||
for single_cmd in midi_cvt.form_progress_bar(
|
||||
maxscore, scoreboard_name, progressbar_style
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
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}[score].mcpack",
|
||||
)
|
||||
|
||||
shutil.rmtree(f"{dist_path}/temp/")
|
||||
|
||||
return maxlen, maxscore
|
||||
|
||||
|
||||
def to_addon_pack_in_delay(
|
||||
midi_cvt: MidiConvert,
|
||||
dist_path: str,
|
||||
progressbar_style: Optional[ProgressBarStyle],
|
||||
player: str = "@a",
|
||||
max_height: int = 64,
|
||||
) -> Tuple[int, int]:
|
||||
"""
|
||||
将midi以延迟播放器形式转换为mcstructure结构文件后打包成附加包,并在附加包中生成相应地导入函数
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
dist_path: str
|
||||
转换结果输出的目标路径
|
||||
progressbar_style: ProgressBarStyle 对象
|
||||
进度条对象
|
||||
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
|
||||
)
|
||||
|
||||
command_list, max_delay = midi_cvt.to_command_list_in_delay(
|
||||
player_selector=player,
|
||||
)[:2]
|
||||
|
||||
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,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
# 写入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"
|
||||
)
|
||||
|
||||
struct, size, end_pos = commands_to_structure(
|
||||
command_list,
|
||||
max_height - 1,
|
||||
compability_version_=compability_ver,
|
||||
)
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.music_name}_main.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct.dump(f)
|
||||
|
||||
del struct
|
||||
|
||||
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)
|
||||
struct_a.set_block(
|
||||
(0, 0, 0),
|
||||
form_command_block_in_NBT_struct(
|
||||
r"scoreboard players add {} {} 1".format(player, scb_name),
|
||||
(0, 0, 0),
|
||||
1,
|
||||
1,
|
||||
alwaysRun=False,
|
||||
customName="显示进度条并加分",
|
||||
compability_version_number=compability_ver,
|
||||
),
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.music_name}_start.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct_a.dump(f)
|
||||
|
||||
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, progressbar_style),
|
||||
max_height - 1,
|
||||
compability_version_=compability_ver,
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.music_name}_pgb.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
pgb_struct.dump(f)
|
||||
|
||||
index_file.write(f"structure load {midi_cvt.music_name}_pgb ~ ~1 ~1\n")
|
||||
|
||||
struct_a = Structure(
|
||||
(1, 1, 1),
|
||||
)
|
||||
struct_a.set_block(
|
||||
(0, 0, 0),
|
||||
form_command_block_in_NBT_struct(
|
||||
r"scoreboard players reset {} {}".format(player, scb_name),
|
||||
(0, 0, 0),
|
||||
1,
|
||||
0,
|
||||
alwaysRun=False,
|
||||
customName="重置进度条计分板",
|
||||
compability_version_number=compability_ver,
|
||||
),
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.music_name}_reset.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct_a.dump(f)
|
||||
|
||||
del struct_a, pgb_struct
|
||||
|
||||
index_file.write(
|
||||
f"structure load {midi_cvt.music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
|
||||
)
|
||||
|
||||
index_file.write(
|
||||
f"structure load {midi_cvt.music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
|
||||
)
|
||||
|
||||
else:
|
||||
index_file.write(f"structure load {midi_cvt.music_name}_main ~ ~ ~1\n")
|
||||
|
||||
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}[delay].mcpack",
|
||||
)
|
||||
|
||||
shutil.rmtree(f"{dist_path}/temp/")
|
||||
|
||||
return len(command_list), max_delay
|
||||
|
||||
|
||||
def to_addon_pack_in_repeater(
|
||||
midi_cvt: MidiConvert,
|
||||
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]:
|
||||
"""
|
||||
将midi以中继器播放器形式转换为mcstructure结构文件后打包成附加包,并在附加包中生成相应地导入函数
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
dist_path: str
|
||||
转换结果输出的目标路径
|
||||
progressbar_style: ProgressBarStyle 对象
|
||||
进度条对象
|
||||
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
|
||||
)
|
||||
|
||||
command_list, max_delay, max_together = midi_cvt.to_command_list_in_delay(
|
||||
player_selector=player,
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
struct, size, end_pos = commands_to_redstone_delay_structure(
|
||||
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(
|
||||
dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.music_name}_main.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct.dump(f)
|
||||
|
||||
del struct
|
||||
|
||||
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)
|
||||
struct_a.set_block(
|
||||
(0, 0, 0),
|
||||
form_command_block_in_NBT_struct(
|
||||
r"scoreboard players add {} {} 1".format(player, scb_name),
|
||||
(0, 0, 0),
|
||||
1,
|
||||
1,
|
||||
alwaysRun=False,
|
||||
customName="显示进度条并加分",
|
||||
compability_version_number=compability_ver,
|
||||
),
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.music_name}_start.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct_a.dump(f)
|
||||
|
||||
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, progressbar_style),
|
||||
max_height - 1,
|
||||
compability_version_=compability_ver,
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.music_name}_pgb.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
pgb_struct.dump(f)
|
||||
|
||||
index_file.write(f"structure load {midi_cvt.music_name}_pgb ~ ~1 ~1\n")
|
||||
|
||||
struct_a = Structure(
|
||||
(1, 1, 1),
|
||||
)
|
||||
struct_a.set_block(
|
||||
(0, 0, 0),
|
||||
form_command_block_in_NBT_struct(
|
||||
r"scoreboard players reset {} {}".format(player, scb_name),
|
||||
(0, 0, 0),
|
||||
1,
|
||||
0,
|
||||
alwaysRun=False,
|
||||
customName="重置进度条计分板",
|
||||
compability_version_number=compability_ver,
|
||||
),
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
dist_path,
|
||||
"temp/structures/",
|
||||
f"{midi_cvt.music_name}_reset.mcstructure",
|
||||
)
|
||||
),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct_a.dump(f)
|
||||
|
||||
del struct_a, pgb_struct
|
||||
|
||||
index_file.write(
|
||||
f"structure load {midi_cvt.music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
|
||||
)
|
||||
|
||||
index_file.write(
|
||||
f"structure load {midi_cvt.music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
|
||||
)
|
||||
|
||||
else:
|
||||
index_file.write(f"structure load {midi_cvt.music_name}_main ~ ~ ~1\n")
|
||||
|
||||
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].mcpack",
|
||||
)
|
||||
|
||||
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
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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
|
||||
@@ -4,8 +4,8 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -18,7 +18,7 @@ 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_MOVE_KEY = {
|
||||
@@ -60,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)
|
||||
|
||||
@@ -123,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(
|
||||
@@ -2,8 +2,8 @@
|
||||
"""
|
||||
用以生成BDX结构文件的附加功能
|
||||
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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 ...old_main import MidiConvert
|
||||
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"))
|
||||
40
old-things/Musicreater/old_plugin/common.py
Normal file
40
old-things/Musicreater/old_plugin/common.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放通用的普遍性的插件内容
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
import math
|
||||
|
||||
|
||||
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)))
|
||||
92
old-things/Musicreater/old_plugin/main.py
Normal file
92
old-things/Musicreater/old_plugin/main.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放附加内容功能
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
# from dataclasses import dataclass
|
||||
# from typing import Literal, Tuple, Union
|
||||
|
||||
# from ..subclass import DEFAULT_PROGRESSBAR_STYLE, ProgressBarStyle
|
||||
|
||||
|
||||
# @dataclass(init=False)
|
||||
# class ConvertConfig: # 必定要改
|
||||
# """
|
||||
# 转换通用设置存储类
|
||||
# """
|
||||
|
||||
# progressbar_style: Union[ProgressBarStyle, None]
|
||||
# """进度条样式"""
|
||||
|
||||
# dist_path: str
|
||||
# """输出目录"""
|
||||
|
||||
# def __init__(
|
||||
# self,
|
||||
# output_path: str,
|
||||
# progressbar: Union[bool, Tuple[str, Tuple[str, str]], ProgressBarStyle] = True,
|
||||
# ignore_progressbar_param_error: bool = False,
|
||||
# ):
|
||||
# """
|
||||
# 将已经转换好的数据内容指令载入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.progressbar_style = None
|
||||
# """进度条样式组"""
|
||||
@@ -2,8 +2,8 @@
|
||||
"""
|
||||
用以生成单个mcstructure文件的附加功能
|
||||
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -18,7 +18,13 @@ __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, to_mcstructure_file_in_score
|
||||
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,
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -14,10 +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 SingleCommand
|
||||
from ...old_main import MidiConvert
|
||||
from ...subclass import MineCommand
|
||||
from ..mcstructure import (
|
||||
COMPABILITY_VERSION_117,
|
||||
COMPABILITY_VERSION_119,
|
||||
@@ -28,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,
|
||||
):
|
||||
@@ -39,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
|
||||
@@ -58,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)
|
||||
@@ -83,7 +77,7 @@ def to_mcstructure_file_in_delay(
|
||||
|
||||
def to_mcstructure_file_in_score(
|
||||
midi_cvt: MidiConvert,
|
||||
data_cfg: ConvertConfig,
|
||||
dist_path: str,
|
||||
scoreboard_name: str = "mscplay",
|
||||
auto_reset: bool = False,
|
||||
max_height: int = 64,
|
||||
@@ -95,8 +89,8 @@ def to_mcstructure_file_in_score(
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
data_cfg: ConvertConfig 对象
|
||||
部分转换通用参数
|
||||
dist_path: str
|
||||
转换结果输出的目标路径
|
||||
scoreboard_name: str
|
||||
我的世界的计分板名称
|
||||
auto_reset: bool
|
||||
@@ -116,18 +110,17 @@ def to_mcstructure_file_in_score(
|
||||
)
|
||||
|
||||
cmd_list, cmd_count, max_delay = 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)
|
||||
|
||||
struct, size, end_pos = commands_to_structure(
|
||||
midi_cvt.music_command_list+(
|
||||
midi_cvt.music_command_list
|
||||
+ (
|
||||
[
|
||||
SingleCommand(
|
||||
MineCommand(
|
||||
command="scoreboard players reset @a[scores={"
|
||||
+ scoreboard_name
|
||||
+ "="
|
||||
@@ -139,13 +132,13 @@ def to_mcstructure_file_in_score(
|
||||
]
|
||||
if auto_reset
|
||||
else []
|
||||
), max_height - 1, compability_version_=compability_ver
|
||||
),
|
||||
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}[score].mcstructure")),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct.dump(f)
|
||||
@@ -155,7 +148,7 @@ def to_mcstructure_file_in_score(
|
||||
|
||||
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",
|
||||
@@ -167,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-"]
|
||||
@@ -188,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,
|
||||
@@ -206,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
|
||||
@@ -4,8 +4,8 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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,50 +164,58 @@ 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(
|
||||
@@ -209,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,
|
||||
@@ -226,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(
|
||||
@@ -313,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",
|
||||
@@ -321,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
|
||||
@@ -4,8 +4,8 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -16,9 +16,9 @@ Terms & Conditions: License.md in the root directory
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
from ..exceptions import NotDefineProgramError, ZeroSpeedError
|
||||
from ..main import MidiConvert
|
||||
from ..subclass import SingleCommand
|
||||
from ..old_exceptions import NotDefineProgramError, ZeroSpeedError
|
||||
from ..old_main import MidiConvert
|
||||
from ..subclass import MineCommand
|
||||
from ..utils import inst_to_sould_with_deviation, perc_inst_to_soundID_withX
|
||||
|
||||
# 你以为写完了吗?其实并没有
|
||||
@@ -4,8 +4,8 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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
|
||||
@@ -2,8 +2,8 @@
|
||||
"""
|
||||
用以生成Schematic结构的附加功能
|
||||
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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 *
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -2,8 +2,8 @@
|
||||
"""
|
||||
用以启动WebSocket服务器播放的附加功能
|
||||
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 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 *
|
||||
|
||||
142
old-things/Musicreater/old_plugin/websocket/main.py
Normal file
142
old-things/Musicreater/old_plugin/websocket/main.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
import uuid
|
||||
from typing import List, Literal, Optional, Tuple
|
||||
|
||||
import fcwslib
|
||||
|
||||
from ...old_main import MidiConvert
|
||||
from ...subclass import MineCommand, ProgressBarStyle
|
||||
|
||||
|
||||
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())
|
||||
73
old-things/Musicreater/old_types.py
Normal file
73
old-things/Musicreater/old_types.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存放数据类型的定义
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
|
||||
|
||||
from .subclass import MineNote
|
||||
|
||||
MidiNoteNameTableType = Mapping[int, Tuple[str, ...]]
|
||||
"""
|
||||
Midi音符名称对照表类型
|
||||
"""
|
||||
|
||||
MidiInstrumentTableType = Mapping[int, str]
|
||||
"""
|
||||
Midi乐器对照表类型
|
||||
"""
|
||||
|
||||
FittingFunctionType = Callable[[float], float]
|
||||
"""
|
||||
拟合函数类型
|
||||
"""
|
||||
|
||||
ChannelType = Dict[
|
||||
int,
|
||||
Dict[
|
||||
int,
|
||||
List[
|
||||
Union[
|
||||
Tuple[Literal["PgmC"], int, int],
|
||||
Tuple[Literal["NoteS"], int, int, int],
|
||||
Tuple[Literal["NoteE"], int, int],
|
||||
]
|
||||
],
|
||||
],
|
||||
]
|
||||
"""
|
||||
以字典所标记的通道信息类型(已弃用)
|
||||
|
||||
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
|
||||
"""
|
||||
|
||||
|
||||
MineNoteChannelType = Mapping[
|
||||
int,
|
||||
List[MineNote,],
|
||||
]
|
||||
"""
|
||||
我的世界通道信息类型
|
||||
|
||||
Dict[int,Dict[int,List[MineNote,],],]
|
||||
"""
|
||||
|
||||
MineNoteTrackType = Mapping[
|
||||
int,
|
||||
List[MineNote,],
|
||||
]
|
||||
|
||||
|
||||
861
old-things/Musicreater/subclass.py
Normal file
861
old-things/Musicreater/subclass.py
Normal file
@@ -0,0 +1,861 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储音·创附属子类
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 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
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Any, List, Tuple, Union, Dict, Sequence
|
||||
|
||||
from .constants import MC_PITCHED_INSTRUMENT_LIST
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class MineNote:
|
||||
"""存储单个音符的类"""
|
||||
|
||||
sound_name: str
|
||||
"""乐器ID"""
|
||||
|
||||
note_pitch: int
|
||||
"""midi音高"""
|
||||
|
||||
velocity: int
|
||||
"""力度"""
|
||||
|
||||
start_tick: int
|
||||
"""开始之时 命令刻"""
|
||||
|
||||
duration: 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,
|
||||
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] = {},
|
||||
):
|
||||
"""
|
||||
用于存储单个音符的类
|
||||
|
||||
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 = (
|
||||
(mc_sound_name not in MC_PITCHED_INSTRUMENT_LIST)
|
||||
if (is_percussion is None)
|
||||
else is_percussion
|
||||
)
|
||||
"""是否为打击乐器"""
|
||||
|
||||
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
|
||||
)
|
||||
"""声源距离"""
|
||||
|
||||
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 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),
|
||||
)
|
||||
|
||||
@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 [
|
||||
self.percussive,
|
||||
self.sound_name,
|
||||
self.note_pitch,
|
||||
self.velocity,
|
||||
self.start_tick,
|
||||
self.duration,
|
||||
self.sound_distance,
|
||||
self.sound_azimuth,
|
||||
]
|
||||
)
|
||||
|
||||
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.sound_name,
|
||||
self.velocity,
|
||||
self.start_tick,
|
||||
self.duration,
|
||||
self.sound_distance,
|
||||
self.sound_azimuth,
|
||||
)
|
||||
if self.percussive
|
||||
else (
|
||||
self.percussive,
|
||||
self.sound_name,
|
||||
self.note_pitch,
|
||||
self.velocity,
|
||||
self.start_tick,
|
||||
self.duration,
|
||||
self.sound_distance,
|
||||
self.sound_azimuth,
|
||||
)
|
||||
)
|
||||
|
||||
def __dict__(self):
|
||||
return (
|
||||
{
|
||||
"Percussive": self.percussive,
|
||||
"Instrument": self.sound_name,
|
||||
"Velocity": self.velocity,
|
||||
"StartTick": self.start_tick,
|
||||
"Duration": self.duration,
|
||||
"SoundDistance": self.sound_distance,
|
||||
"SoundAzimuth": self.sound_azimuth,
|
||||
"ExtraData": self.extra_info,
|
||||
}
|
||||
if self.percussive
|
||||
else {
|
||||
"Percussive": self.percussive,
|
||||
"Instrument": self.sound_name,
|
||||
"Pitch": self.note_pitch,
|
||||
"Velocity": self.velocity,
|
||||
"StartTick": self.start_tick,
|
||||
"Duration": self.duration,
|
||||
"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.tuplize() == other.tuplize()
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class MineCommand:
|
||||
"""存储单个指令的类"""
|
||||
|
||||
command_text: str
|
||||
"""指令文本"""
|
||||
|
||||
conditional: bool
|
||||
"""执行是否有条件"""
|
||||
|
||||
delay: int
|
||||
"""执行的延迟"""
|
||||
|
||||
annotation_text: str
|
||||
"""指令注释"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
command: str,
|
||||
condition: bool = False,
|
||||
tick_delay: int = 0,
|
||||
annotation: str = "",
|
||||
):
|
||||
"""
|
||||
存储单个指令的类
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command: str
|
||||
指令
|
||||
condition: bool
|
||||
是否有条件
|
||||
tick_delay: int
|
||||
执行延时
|
||||
annotation: str
|
||||
注释
|
||||
"""
|
||||
self.command_text = command
|
||||
self.conditional = condition
|
||||
self.delay = tick_delay
|
||||
self.annotation_text = annotation
|
||||
|
||||
def copy(self):
|
||||
return MineCommand(
|
||||
command=self.command_text,
|
||||
condition=self.conditional,
|
||||
tick_delay=self.delay,
|
||||
annotation=self.annotation_text,
|
||||
)
|
||||
|
||||
@property
|
||||
def cmd(self) -> str:
|
||||
"""
|
||||
我的世界函数字符串(包含注释)
|
||||
"""
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
转为我的世界函数文件格式(包含注释)
|
||||
"""
|
||||
return "# {cdt}<{delay}> {ant}\n{cmd}".format(
|
||||
cdt="[CDT]" if self.conditional else "",
|
||||
delay=self.delay,
|
||||
ant=self.annotation_text,
|
||||
cmd=self.command_text,
|
||||
)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
return self.__str__() == other.__str__()
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class SingleNoteBox:
|
||||
"""存储单个音符盒"""
|
||||
|
||||
instrument_block: str
|
||||
"""乐器方块"""
|
||||
|
||||
note_value: int
|
||||
"""音符盒音高"""
|
||||
|
||||
annotation_text: str
|
||||
"""音符注释"""
|
||||
|
||||
is_percussion: bool
|
||||
"""是否为打击乐器"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
instrument_block_: str,
|
||||
note_value_: int,
|
||||
percussion: Optional[bool] = None,
|
||||
annotation: str = "",
|
||||
):
|
||||
"""
|
||||
用于存储单个音符盒的类
|
||||
|
||||
Parameters
|
||||
------------
|
||||
instrument_block_: str
|
||||
音符盒演奏所使用的乐器方块
|
||||
note_value_: int
|
||||
音符盒的演奏音高
|
||||
percussion: bool
|
||||
此音符盒乐器是否作为打击乐处理
|
||||
注:若为空,则自动识别是否为打击乐器
|
||||
annotation: Any
|
||||
音符注释
|
||||
|
||||
Returns
|
||||
---------
|
||||
SingleNoteBox 类
|
||||
"""
|
||||
|
||||
self.instrument_block = instrument_block_
|
||||
"""乐器方块"""
|
||||
self.note_value = note_value_
|
||||
"""音符盒音高"""
|
||||
self.annotation_text = annotation
|
||||
"""音符注释"""
|
||||
if percussion is None:
|
||||
self.is_percussion = percussion not in MC_PITCHED_INSTRUMENT_LIST
|
||||
else:
|
||||
self.is_percussion = percussion
|
||||
|
||||
@property
|
||||
def inst(self) -> str:
|
||||
"""获取音符盒下的乐器方块"""
|
||||
return self.instrument_block
|
||||
|
||||
@inst.setter
|
||||
def inst(self, inst_):
|
||||
self.instrument_block = inst_
|
||||
|
||||
@property
|
||||
def note(self) -> int:
|
||||
"""获取音符盒音调特殊值"""
|
||||
return self.note_value
|
||||
|
||||
@note.setter
|
||||
def note(self, note_):
|
||||
self.note_value = note_
|
||||
|
||||
@property
|
||||
def annotation(self) -> str:
|
||||
"""获取音符盒的备注"""
|
||||
return self.annotation_text
|
||||
|
||||
@annotation.setter
|
||||
def annotation(self, annotation_):
|
||||
self.annotation_text = annotation_
|
||||
|
||||
def copy(self):
|
||||
return SingleNoteBox(
|
||||
instrument_block_=self.instrument_block,
|
||||
note_value_=self.note_value,
|
||||
annotation=self.annotation_text,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Note(inst = {self.inst}, note = {self.note}, )"
|
||||
|
||||
def __tuple__(self) -> tuple:
|
||||
return self.inst, self.note, self.annotation
|
||||
|
||||
def __dict__(self) -> dict:
|
||||
return {
|
||||
"inst": self.inst,
|
||||
"note": self.note,
|
||||
"annotation": self.annotation,
|
||||
}
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
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"=",
|
||||
)
|
||||
"""
|
||||
默认的进度条样式
|
||||
"""
|
||||
1029
old-things/Musicreater/utils.py
Normal file
1029
old-things/Musicreater/utils.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
old-things/Packer/MSCT_MAIN.MPK
Normal file
BIN
old-things/Packer/MSCT_MAIN.MPK
Normal file
Binary file not shown.
BIN
old-things/Packer/MSCT_PLUGIN.MPK
Normal file
BIN
old-things/Packer/MSCT_PLUGIN.MPK
Normal file
Binary file not shown.
1
old-things/Packer/MSCT_PLUGIN_FUNCTION.MPK
Normal file
1
old-things/Packer/MSCT_PLUGIN_FUNCTION.MPK
Normal file
@@ -0,0 +1 @@
|
||||
q@v,fxіБ<D196>Еџ<D095>лцmЩ5]Ќs"ЏџЦбBMXi<58>ЈnНхч<D185>Z8О=Г<7F>4<EFBFBD>PTUБQЈmтфджG<D0B6>жu_цп<D186>DS№|
|
||||
@@ -1,29 +1,27 @@
|
||||
import Musicreater
|
||||
import Musicreater.old_init as old_init
|
||||
import Musicreater.experiment
|
||||
import Musicreater.previous
|
||||
import Musicreater.old_plugin
|
||||
|
||||
import Musicreater.plugin
|
||||
|
||||
from Musicreater.plugin.addonpack import (
|
||||
# import Musicreater.previous
|
||||
from Musicreater.old_plugin.addonpack import (
|
||||
to_addon_pack_in_delay,
|
||||
to_addon_pack_in_repeater,
|
||||
to_addon_pack_in_score,
|
||||
)
|
||||
from Musicreater.plugin.mcstructfile import (
|
||||
from Musicreater.old_plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||
from Musicreater.old_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
|
||||
|
||||
MSCT_MAIN = (
|
||||
Musicreater,
|
||||
Musicreater.experiment,
|
||||
Musicreater.previous,
|
||||
old_init,
|
||||
old_init.experiment,
|
||||
# Musicreater.previous,
|
||||
)
|
||||
|
||||
MSCT_PLUGIN = (Musicreater.plugin,)
|
||||
MSCT_PLUGIN = (old_init.old_plugin,)
|
||||
|
||||
MSCT_PLUGIN_FUNCTION = (
|
||||
to_addon_pack_in_delay,
|
||||
@@ -38,11 +36,11 @@ MSCT_PLUGIN_FUNCTION = (
|
||||
|
||||
import hashlib
|
||||
|
||||
import dill
|
||||
import brotli
|
||||
import dill
|
||||
|
||||
|
||||
def enpack_llc_pack(sth, to_dist: str):
|
||||
def enpack_msct_pack(sth, to_dist: str):
|
||||
packing_bytes = brotli.compress(
|
||||
dill.dumps(
|
||||
sth,
|
||||
@@ -59,12 +57,12 @@ def enpack_llc_pack(sth, to_dist: str):
|
||||
|
||||
with open("./Packer/checksum.txt", "w", encoding="utf-8") as f:
|
||||
f.write("MSCT_MAIN:\n")
|
||||
f.write(enpack_llc_pack(MSCT_MAIN, "./Packer/MSCT_MAIN.MPK").hexdigest())
|
||||
f.write(enpack_msct_pack(MSCT_MAIN, "./Packer/MSCT_MAIN.MPK").hexdigest())
|
||||
f.write("\nMSCT_PLUGIN:\n")
|
||||
f.write(enpack_llc_pack(MSCT_PLUGIN, "./Packer/MSCT_PLUGIN.MPK").hexdigest())
|
||||
f.write(enpack_msct_pack(MSCT_PLUGIN, "./Packer/MSCT_PLUGIN.MPK").hexdigest())
|
||||
f.write("\nMSCT_PLUGIN_FUNCTION:\n")
|
||||
f.write(
|
||||
enpack_llc_pack(
|
||||
enpack_msct_pack(
|
||||
MSCT_PLUGIN_FUNCTION, "./Packer/MSCT_PLUGIN_FUNCTION.MPK"
|
||||
).hexdigest()
|
||||
)
|
||||
6
old-things/Packer/checksum.txt
Normal file
6
old-things/Packer/checksum.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
MSCT_MAIN:
|
||||
6b9f5a97d50beb07c834e375104c67ae44c57ae40f73fb71075b3668899029c7
|
||||
MSCT_PLUGIN:
|
||||
c280413a394a539438a5d10078c9b55f04bcd4cf6869c59a3f7a026039748cfc
|
||||
MSCT_PLUGIN_FUNCTION:
|
||||
40697f1d9b293268fe142fa3e9bffee2923a8f4811ec7bbdf7b14afb98723ef2
|
||||
0
old-things/bgArrayLib/__init__.py
Normal file
0
old-things/bgArrayLib/__init__.py
Normal file
67
old-things/bgArrayLib/bpm.py
Normal file
67
old-things/bgArrayLib/bpm.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import mido
|
||||
import numpy
|
||||
|
||||
'''
|
||||
bpm
|
||||
bites per minutes
|
||||
每分钟的拍数
|
||||
'''
|
||||
|
||||
def mt2gt(mt, tpb_a, bpm_a):
|
||||
return round(mt / tpb_a / bpm_a * 60)
|
||||
|
||||
|
||||
def get(mid:mido.MidiFile) -> int:
|
||||
'''传入一个 MidiFile, 返回其音乐的bpm
|
||||
:param mid : mido.MidFile
|
||||
mido库识别的midi文件数据
|
||||
:return bpm : int
|
||||
'''
|
||||
# mid = mido.MidiFile(mf)
|
||||
length = mid.length
|
||||
tpb = mid.ticks_per_beat
|
||||
bpm = 20
|
||||
gotV = 0
|
||||
|
||||
for track in mid.tracks:
|
||||
global_time = 0
|
||||
for msg in track:
|
||||
global_time += msg.time
|
||||
if msg.type == "note_on" and msg.velocity > 0:
|
||||
gotV = mt2gt(global_time, tpb, bpm)
|
||||
errorV = numpy.fabs(gotV - length)
|
||||
last_dic = {bpm: errorV}
|
||||
if last_dic.get(bpm) > errorV:
|
||||
last_dic = {bpm: errorV}
|
||||
bpm += 2
|
||||
|
||||
while True:
|
||||
for track in mid.tracks:
|
||||
global_time = 0
|
||||
for msg in track:
|
||||
global_time += msg.time
|
||||
if msg.type == "note_on" and msg.velocity > 0:
|
||||
gotV = mt2gt(global_time, tpb, bpm)
|
||||
errorV = numpy.fabs(gotV - length)
|
||||
try:
|
||||
if last_dic.get(bpm - 2) > errorV:
|
||||
last_dic = {bpm: errorV}
|
||||
except TypeError:
|
||||
pass
|
||||
bpm += 2
|
||||
if bpm >= 252:
|
||||
break
|
||||
print(list(last_dic.keys())[0])
|
||||
return list(last_dic.keys())[0]
|
||||
|
||||
|
||||
def compute(mid:mido.MidiFile):
|
||||
answer = 60000000/mid.ticks_per_beat
|
||||
print(answer)
|
||||
return answer
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
mid = mido.MidiFile(r"C:\Users\lc\Documents\MuseScore3\乐谱\乐谱\Bad style - Time back.mid")
|
||||
get(mid)
|
||||
compute(mid)
|
||||
40
old-things/bgArrayLib/compute.py
Normal file
40
old-things/bgArrayLib/compute.py
Normal file
@@ -0,0 +1,40 @@
|
||||
def round_up(num, power=0):
|
||||
"""
|
||||
实现精确四舍五入,包含正、负小数多种场景
|
||||
:param num: 需要四舍五入的小数
|
||||
:param power: 四舍五入位数,支持0-∞
|
||||
:return: 返回四舍五入后的结果
|
||||
"""
|
||||
try:
|
||||
print(1 / 0)
|
||||
except ZeroDivisionError:
|
||||
digit = 10 ** power
|
||||
num2 = float(int(num * digit))
|
||||
# 处理正数,power不为0的情况
|
||||
if num >= 0 and power != 0:
|
||||
tag = num * digit - num2 + 1 / (digit * 10)
|
||||
if tag >= 0.5:
|
||||
return (num2 + 1) / digit
|
||||
else:
|
||||
return num2 / digit
|
||||
# 处理正数,power为0取整的情况
|
||||
elif num >= 0 and power == 0:
|
||||
tag = num * digit - int(num)
|
||||
if tag >= 0.5:
|
||||
return (num2 + 1) / digit
|
||||
else:
|
||||
return num2 / digit
|
||||
# 处理负数,power为0取整的情况
|
||||
elif power == 0 and num < 0:
|
||||
tag = num * digit - int(num)
|
||||
if tag <= -0.5:
|
||||
return (num2 - 1) / digit
|
||||
else:
|
||||
return num2 / digit
|
||||
# 处理负数,power不为0的情况
|
||||
else:
|
||||
tag = num * digit - num2 - 1 / (digit * 10)
|
||||
if tag <= -0.5:
|
||||
return (num2 - 1) / digit
|
||||
else:
|
||||
return num2 / digit
|
||||
130
old-things/bgArrayLib/instrumentConstant.py
Normal file
130
old-things/bgArrayLib/instrumentConstant.py
Normal file
@@ -0,0 +1,130 @@
|
||||
instrument_list = {
|
||||
"0": "harp",
|
||||
"1": "harp",
|
||||
"2": "pling",
|
||||
"3": "harp",
|
||||
"4": "pling",
|
||||
"5": "pling",
|
||||
"6": "harp",
|
||||
"7": "harp",
|
||||
"8": "share",
|
||||
"9": "harp",
|
||||
"10": "didgeridoo",
|
||||
"11": "harp",
|
||||
"12": "xylophone",
|
||||
"13": "chime",
|
||||
"14": "harp",
|
||||
"15": "harp",
|
||||
"16": "bass",
|
||||
"17": "harp",
|
||||
"18": "harp",
|
||||
"19": "harp",
|
||||
"20": "harp",
|
||||
"21": "harp",
|
||||
"22": "harp",
|
||||
"23": "guitar",
|
||||
"24": "guitar",
|
||||
"25": "guitar",
|
||||
"26": "guitar",
|
||||
"27": "guitar",
|
||||
"28": "guitar",
|
||||
"29": "guitar",
|
||||
"30": "guitar",
|
||||
"31": "bass",
|
||||
"32": "bass",
|
||||
"33": "bass",
|
||||
"34": "bass",
|
||||
"35": "bass",
|
||||
"36": "bass",
|
||||
"37": "bass",
|
||||
"38": "bass",
|
||||
"39": "bass",
|
||||
"40": "harp",
|
||||
"41": "harp",
|
||||
"42": "harp",
|
||||
"43": "harp",
|
||||
"44": "iron_xylophone",
|
||||
"45": "guitar",
|
||||
"46": "harp",
|
||||
"47": "harp",
|
||||
"48": "guitar",
|
||||
"49": "guitar",
|
||||
"50": "bit",
|
||||
"51": "bit",
|
||||
"52": "harp",
|
||||
"53": "harp",
|
||||
"54": "bit",
|
||||
"55": "flute",
|
||||
"56": "flute",
|
||||
"57": "flute",
|
||||
"58": "flute",
|
||||
"59": "flute",
|
||||
"60": "flute",
|
||||
"61": "flute",
|
||||
"62": "flute",
|
||||
"63": "flute",
|
||||
"64": "bit",
|
||||
"65": "bit",
|
||||
"66": "bit",
|
||||
"67": "bit",
|
||||
"68": "flute",
|
||||
"69": "harp",
|
||||
"70": "harp",
|
||||
"71": "flute",
|
||||
"72": "flute",
|
||||
"73": "flute",
|
||||
"74": "harp",
|
||||
"75": "flute",
|
||||
"76": "harp",
|
||||
"77": "harp",
|
||||
"78": "harp",
|
||||
"79": "harp",
|
||||
"80": "bit",
|
||||
"81": "bit",
|
||||
"82": "bit",
|
||||
"83": "bit",
|
||||
"84": "bit",
|
||||
"85": "bit",
|
||||
"86": "bit",
|
||||
"87": "bit",
|
||||
"88": "bit",
|
||||
"89": "bit",
|
||||
"90": "bit",
|
||||
"91": "bit",
|
||||
"92": "bit",
|
||||
"93": "bit",
|
||||
"94": "bit",
|
||||
"95": "bit",
|
||||
"96": "bit",
|
||||
"97": "bit",
|
||||
"98": "bit",
|
||||
"99": "bit",
|
||||
"100": "bit",
|
||||
"101": "bit",
|
||||
"102": "bit",
|
||||
"103": "bit",
|
||||
"104": "harp",
|
||||
"105": "banjo",
|
||||
"106": "harp",
|
||||
"107": "harp",
|
||||
"108": "harp",
|
||||
"109": "harp",
|
||||
"110": "harp",
|
||||
"111": "guitar",
|
||||
"112": "harp",
|
||||
"113": "bell",
|
||||
"114": "harp",
|
||||
"115": "cow_bell",
|
||||
"116": "basedrum",
|
||||
"117": "bass",
|
||||
"118": "bit",
|
||||
"119": "basedrum",
|
||||
"120": "guitar",
|
||||
"121": "harp",
|
||||
"122": "harp",
|
||||
"123": "harp",
|
||||
"124": "harp",
|
||||
"125": "hat",
|
||||
"126": "basedrum",
|
||||
"127": "snare",
|
||||
}
|
||||
250
old-things/bgArrayLib/namesConstant.py
Normal file
250
old-things/bgArrayLib/namesConstant.py
Normal file
@@ -0,0 +1,250 @@
|
||||
zip_name = {
|
||||
-1: "-1.Acoustic_Kit_打击乐.zip",
|
||||
0: "0.Acoustic_Grand_Piano_大钢琴.zip",
|
||||
1: "1.Bright_Acoustic_Piano_亮音大钢琴.zip",
|
||||
10: "10.Music_Box_八音盒.zip",
|
||||
100: "100.FX_brightness_合成特效-亮音.zip",
|
||||
101: "101.FX_goblins_合成特效-小妖.zip",
|
||||
102: "102.FX_echoes_合成特效-回声.zip",
|
||||
103: "103.FX_sci-fi_合成特效-科幻.zip",
|
||||
104: "104.Sitar_锡塔尔.zip",
|
||||
105: "105.Banjo_班卓.zip",
|
||||
106: "106.Shamisen_三味线.zip",
|
||||
107: "107.Koto_筝.zip",
|
||||
108: "108.Kalimba_卡林巴.zip",
|
||||
109: "109.Bagpipe_风笛.zip",
|
||||
11: "11.Vibraphone_电颤琴.zip",
|
||||
110: "110.Fiddle_古提琴.zip",
|
||||
111: "111.Shanai_唢呐.zip",
|
||||
112: "112.Tinkle_Bell_铃铛.zip",
|
||||
113: "113.Agogo_拉丁打铃.zip",
|
||||
114: "114.Steel_Drums_钢鼓.zip",
|
||||
115: "115.Woodblock_木块.zip",
|
||||
116: "116.Taiko_Drum_太鼓.zip",
|
||||
117: "117.Melodic_Tom_嗵鼓.zip",
|
||||
118: "118.Synth_Drum_合成鼓.zip",
|
||||
119: "119.Reverse_Cymbal_镲波形反转.zip",
|
||||
12: "12.Marimba_马林巴.zip",
|
||||
13: "13.Xylophone_木琴.zip",
|
||||
14: "14.Tubular_Bells_管钟.zip",
|
||||
15: "15.Dulcimer_扬琴.zip",
|
||||
16: "16.Drawbar_Organ_击杆风琴.zip",
|
||||
17: "17.Percussive_Organ_打击型风琴.zip",
|
||||
18: "18.Rock_Organ_摇滚风琴.zip",
|
||||
19: "19.Church_Organ_管风琴.zip",
|
||||
2: "2.Electric_Grand_Piano_电子大钢琴.zip",
|
||||
20: "20.Reed_Organ_簧风琴.zip",
|
||||
21: "21.Accordion_手风琴.zip",
|
||||
22: "22.Harmonica_口琴.zip",
|
||||
23: "23.Tango_Accordian_探戈手风琴.zip",
|
||||
24: "24.Acoustic_Guitar_(nylon)_尼龙弦吉他.zip",
|
||||
25: "25.Acoustic_Guitar(steel)_钢弦吉他.zip",
|
||||
26: "26.Electric_Guitar_(jazz)_爵士乐电吉他.zip",
|
||||
27: "27.Electric_Guitar_(clean)_清音电吉他.zip",
|
||||
28: "28.Electric_Guitar_(muted)_弱音电吉他.zip",
|
||||
29: "29.Overdriven_Guitar_驱动音效吉他.zip",
|
||||
3: "3.Honky-Tonk_Piano_酒吧钢琴.zip",
|
||||
30: "30.Distortion_Guitar_失真音效吉他.zip",
|
||||
31: "31.Guitar_Harmonics_吉他泛音.zip",
|
||||
32: "32.Acoustic_Bass_原声贝司.zip",
|
||||
33: "33.Electric_Bass(finger)_指拨电贝司.zip",
|
||||
34: "34.Electric_Bass(pick)_拨片拨电贝司.zip",
|
||||
35: "35.Fretless_Bass_无品贝司.zip",
|
||||
36: "36.Slap_Bass_A_击弦贝司A.zip",
|
||||
37: "37.Slap_Bass_B_击弦贝司B.zip",
|
||||
38: "38.Synth_Bass_A_合成贝司A.zip",
|
||||
39: "39.Synth_Bass_B_合成贝司B.zip",
|
||||
4: "4.Electric_Piano_1_电钢琴A.zip",
|
||||
40: "40.Violin_小提琴.zip",
|
||||
41: "41.Viola_中提琴.zip",
|
||||
42: "42.Cello_大提琴.zip",
|
||||
43: "43.Contrabass_低音提琴.zip",
|
||||
44: "44.Tremolo_Strings_弦乐震音.zip",
|
||||
45: "45.Pizzicato_Strings_弦乐拨奏.zip",
|
||||
46: "46.Orchestral_Harp_竖琴.zip",
|
||||
47: "47.Timpani_定音鼓.zip",
|
||||
48: "48.String_Ensemble_A_弦乐合奏A.zip",
|
||||
49: "49.String_Ensemble_B_弦乐合奏B.zip",
|
||||
5: "5.Electric_Piano_2_电钢琴B.zip",
|
||||
50: "50.SynthStrings_A_合成弦乐A.zip",
|
||||
51: "51.SynthStrings_B_合成弦乐B.zip",
|
||||
52: "52.Choir_Aahs_合唱“啊”音.zip",
|
||||
53: "53.Voice_Oohs_人声“哦”音.zip",
|
||||
54: "54.Synth_Voice_合成人声.zip",
|
||||
55: "55.Orchestra_Hit_乐队打击乐.zip",
|
||||
56: "56.Trumpet_小号.zip",
|
||||
57: "57.Trombone_长号.zip",
|
||||
58: "58.Tuba_大号.zip",
|
||||
59: "59.Muted_Trumpet_弱音小号.zip",
|
||||
6: "6.Harpsichord_拨弦古钢琴.zip",
|
||||
60: "60.French_Horn_圆号.zip",
|
||||
61: "61.Brass_Section_铜管组.zip",
|
||||
62: "62.Synth_Brass_A_合成铜管A.zip",
|
||||
63: "63.Synth_Brass_A_合成铜管B.zip",
|
||||
64: "64.Soprano_Sax_高音萨克斯.zip",
|
||||
65: "65.Alto_Sax_中音萨克斯.zip",
|
||||
66: "66.Tenor_Sax_次中音萨克斯.zip",
|
||||
67: "67.Baritone_Sax_上低音萨克斯.zip",
|
||||
68: "68.Oboe_双簧管.zip",
|
||||
69: "69.English_Horn_英国管.zip",
|
||||
7: "7.Clavinet_击弦古钢琴.zip",
|
||||
70: "70.Bassoon_大管.zip",
|
||||
71: "71.Clarinet_单簧管.zip",
|
||||
72: "72.Piccolo_短笛.zip",
|
||||
73: "73.Flute_长笛.zip",
|
||||
74: "74.Recorder_竖笛.zip",
|
||||
75: "75.Pan_Flute_排笛.zip",
|
||||
76: "76.Bottle_Blow_吹瓶口.zip",
|
||||
77: "77.Skakuhachi_尺八.zip",
|
||||
78: "78.Whistle_哨.zip",
|
||||
79: "79.Ocarina_洋埙.zip",
|
||||
8: "8.Celesta_钢片琴.zip",
|
||||
80: "80.Lead_square_合成主音-方波.zip",
|
||||
81: "81.Lead_sawtooth_合成主音-锯齿波.zip",
|
||||
82: "82.Lead_calliope_lead_合成主音-汽笛风琴.zip",
|
||||
83: "83.Lead_chiff_lead_合成主音-吹管.zip",
|
||||
84: "84.Lead_charang_合成主音5-吉他.zip",
|
||||
85: "85.Lead_voice_合成主音-人声.zip",
|
||||
86: "86.Lead_fifths_合成主音-五度.zip",
|
||||
87: "87.Lead_bass+lead_合成主音-低音加主音.zip",
|
||||
88: "88.Pad_new_age_合成柔音-新时代.zip",
|
||||
89: "89.Pad_warm_合成柔音-暖音.zip",
|
||||
9: "9.Glockenspiel_钟琴.zip",
|
||||
90: "90.Pad_polysynth_合成柔音-复合成.zip",
|
||||
91: "91.Pad_choir_合成柔音-合唱.zip",
|
||||
92: "92.Pad_bowed_合成柔音-弓弦.zip",
|
||||
93: "93.Pad_metallic_合成柔音-金属.zip",
|
||||
94: "94.Pad_halo_合成柔音-光环.zip",
|
||||
95: "95.Pad_sweep_合成柔音-扫弦.zip",
|
||||
96: "96.FX_rain_合成特效-雨.zip",
|
||||
97: "97.FX_soundtrack_合成特效-音轨.zip",
|
||||
98: "98.FX_crystal_合成特效-水晶.zip",
|
||||
99: "99.FX_atmosphere_合成特效-大气.zip",
|
||||
}
|
||||
|
||||
mcpack_name = {
|
||||
-1: "-1.Acoustic_Kit_打击乐.mcpack",
|
||||
0: "0.Acoustic_Grand_Piano_大钢琴.mcpack",
|
||||
1: "1.Bright_Acoustic_Piano_亮音大钢琴.mcpack",
|
||||
10: "10.Music_Box_八音盒.mcpack",
|
||||
100: "100.FX_brightness_合成特效-亮音.mcpack",
|
||||
101: "101.FX_goblins_合成特效-小妖.mcpack",
|
||||
102: "102.FX_echoes_合成特效-回声.mcpack",
|
||||
103: "103.FX_sci-fi_合成特效-科幻.mcpack",
|
||||
104: "104.Sitar_锡塔尔.mcpack",
|
||||
105: "105.Banjo_班卓.mcpack",
|
||||
106: "106.Shamisen_三味线.mcpack",
|
||||
107: "107.Koto_筝.mcpack",
|
||||
108: "108.Kalimba_卡林巴.mcpack",
|
||||
109: "109.Bagpipe_风笛.mcpack",
|
||||
11: "11.Vibraphone_电颤琴.mcpack",
|
||||
110: "110.Fiddle_古提琴.mcpack",
|
||||
111: "111.Shanai_唢呐.mcpack",
|
||||
112: "112.Tinkle_Bell_铃铛.mcpack",
|
||||
113: "113.Agogo_拉丁打铃.mcpack",
|
||||
114: "114.Steel_Drums_钢鼓.mcpack",
|
||||
115: "115.Woodblock_木块.mcpack",
|
||||
116: "116.Taiko_Drum_太鼓.mcpack",
|
||||
117: "117.Melodic_Tom_嗵鼓.mcpack",
|
||||
118: "118.Synth_Drum_合成鼓.mcpack",
|
||||
119: "119.Reverse_Cymbal_镲波形反转.mcpack",
|
||||
12: "12.Marimba_马林巴.mcpack",
|
||||
13: "13.Xylophone_木琴.mcpack",
|
||||
14: "14.Tubular_Bells_管钟.mcpack",
|
||||
15: "15.Dulcimer_扬琴.mcpack",
|
||||
16: "16.Drawbar_Organ_击杆风琴.mcpack",
|
||||
17: "17.Percussive_Organ_打击型风琴.mcpack",
|
||||
18: "18.Rock_Organ_摇滚风琴.mcpack",
|
||||
19: "19.Church_Organ_管风琴.mcpack",
|
||||
2: "2.Electric_Grand_Piano_电子大钢琴.mcpack",
|
||||
20: "20.Reed_Organ_簧风琴.mcpack",
|
||||
21: "21.Accordion_手风琴.mcpack",
|
||||
22: "22.Harmonica_口琴.mcpack",
|
||||
23: "23.Tango_Accordian_探戈手风琴.mcpack",
|
||||
24: "24.Acoustic_Guitar_(nylon)_尼龙弦吉他.mcpack",
|
||||
25: "25.Acoustic_Guitar(steel)_钢弦吉他.mcpack",
|
||||
26: "26.Electric_Guitar_(jazz)_爵士乐电吉他.mcpack",
|
||||
27: "27.Electric_Guitar_(clean)_清音电吉他.mcpack",
|
||||
28: "28.Electric_Guitar_(muted)_弱音电吉他.mcpack",
|
||||
29: "29.Overdriven_Guitar_驱动音效吉他.mcpack",
|
||||
3: "3.Honky-Tonk_Piano_酒吧钢琴.mcpack",
|
||||
30: "30.Distortion_Guitar_失真音效吉他.mcpack",
|
||||
31: "31.Guitar_Harmonics_吉他泛音.mcpack",
|
||||
32: "32.Acoustic_Bass_原声贝司.mcpack",
|
||||
33: "33.Electric_Bass(finger)_指拨电贝司.mcpack",
|
||||
34: "34.Electric_Bass(pick)_拨片拨电贝司.mcpack",
|
||||
35: "35.Fretless_Bass_无品贝司.mcpack",
|
||||
36: "36.Slap_Bass_A_击弦贝司A.mcpack",
|
||||
37: "37.Slap_Bass_B_击弦贝司B.mcpack",
|
||||
38: "38.Synth_Bass_A_合成贝司A.mcpack",
|
||||
39: "39.Synth_Bass_B_合成贝司B.mcpack",
|
||||
4: "4.Electric_Piano_1_电钢琴A.mcpack",
|
||||
40: "40.Violin_小提琴.mcpack",
|
||||
41: "41.Viola_中提琴.mcpack",
|
||||
42: "42.Cello_大提琴.mcpack",
|
||||
43: "43.Contrabass_低音提琴.mcpack",
|
||||
44: "44.Tremolo_Strings_弦乐震音.mcpack",
|
||||
45: "45.Pizzicato_Strings_弦乐拨奏.mcpack",
|
||||
46: "46.Orchestral_Harp_竖琴.mcpack",
|
||||
47: "47.Timpani_定音鼓.mcpack",
|
||||
48: "48.String_Ensemble_A_弦乐合奏A.mcpack",
|
||||
49: "49.String_Ensemble_B_弦乐合奏B.mcpack",
|
||||
5: "5.Electric_Piano_2_电钢琴B.mcpack",
|
||||
50: "50.SynthStrings_A_合成弦乐A.mcpack",
|
||||
51: "51.SynthStrings_B_合成弦乐B.mcpack",
|
||||
52: "52.Choir_Aahs_合唱“啊”音.mcpack",
|
||||
53: "53.Voice_Oohs_人声“哦”音.mcpack",
|
||||
54: "54.Synth_Voice_合成人声.mcpack",
|
||||
55: "55.Orchestra_Hit_乐队打击乐.mcpack",
|
||||
56: "56.Trumpet_小号.mcpack",
|
||||
57: "57.Trombone_长号.mcpack",
|
||||
58: "58.Tuba_大号.mcpack",
|
||||
59: "59.Muted_Trumpet_弱音小号.mcpack",
|
||||
6: "6.Harpsichord_拨弦古钢琴.mcpack",
|
||||
60: "60.French_Horn_圆号.mcpack",
|
||||
61: "61.Brass_Section_铜管组.mcpack",
|
||||
62: "62.Synth_Brass_A_合成铜管A.mcpack",
|
||||
63: "63.Synth_Brass_A_合成铜管B.mcpack",
|
||||
64: "64.Soprano_Sax_高音萨克斯.mcpack",
|
||||
65: "65.Alto_Sax_中音萨克斯.mcpack",
|
||||
66: "66.Tenor_Sax_次中音萨克斯.mcpack",
|
||||
67: "67.Baritone_Sax_上低音萨克斯.mcpack",
|
||||
68: "68.Oboe_双簧管.mcpack",
|
||||
69: "69.English_Horn_英国管.mcpack",
|
||||
7: "7.Clavinet_击弦古钢琴.mcpack",
|
||||
70: "70.Bassoon_大管.mcpack",
|
||||
71: "71.Clarinet_单簧管.mcpack",
|
||||
72: "72.Piccolo_短笛.mcpack",
|
||||
73: "73.Flute_长笛.mcpack",
|
||||
74: "74.Recorder_竖笛.mcpack",
|
||||
75: "75.Pan_Flute_排笛.mcpack",
|
||||
76: "76.Bottle_Blow_吹瓶口.mcpack",
|
||||
77: "77.Skakuhachi_尺八.mcpack",
|
||||
78: "78.Whistle_哨.mcpack",
|
||||
79: "79.Ocarina_洋埙.mcpack",
|
||||
8: "8.Celesta_钢片琴.mcpack",
|
||||
80: "80.Lead_square_合成主音-方波.mcpack",
|
||||
81: "81.Lead_sawtooth_合成主音-锯齿波.mcpack",
|
||||
82: "82.Lead_calliope_lead_合成主音-汽笛风琴.mcpack",
|
||||
83: "83.Lead_chiff_lead_合成主音-吹管.mcpack",
|
||||
84: "84.Lead_charang_合成主音5-吉他.mcpack",
|
||||
85: "85.Lead_voice_合成主音-人声.mcpack",
|
||||
86: "86.Lead_fifths_合成主音-五度.mcpack",
|
||||
87: "87.Lead_bass+lead_合成主音-低音加主音.mcpack",
|
||||
88: "88.Pad_new_age_合成柔音-新时代.mcpack",
|
||||
89: "89.Pad_warm_合成柔音-暖音.mcpack",
|
||||
9: "9.Glockenspiel_钟琴.mcpack",
|
||||
90: "90.Pad_polysynth_合成柔音-复合成.mcpack",
|
||||
91: "91.Pad_choir_合成柔音-合唱.mcpack",
|
||||
92: "92.Pad_bowed_合成柔音-弓弦.mcpack",
|
||||
93: "93.Pad_metallic_合成柔音-金属.mcpack",
|
||||
94: "94.Pad_halo_合成柔音-光环.mcpack",
|
||||
95: "95.Pad_sweep_合成柔音-扫弦.mcpack",
|
||||
96: "96.FX_rain_合成特效-雨.mcpack",
|
||||
97: "97.FX_soundtrack_合成特效-音轨.mcpack",
|
||||
98: "98.FX_crystal_合成特效-水晶.mcpack",
|
||||
99: "99.FX_atmosphere_合成特效-大气.mcpack",
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(zip_name[0])
|
||||
134
old-things/bgArrayLib/pitchStrConstant.py
Normal file
134
old-things/bgArrayLib/pitchStrConstant.py
Normal file
@@ -0,0 +1,134 @@
|
||||
pitch = {
|
||||
"0": "0.0220970869120796",
|
||||
"1": "0.0234110480761981",
|
||||
"2": "0.0248031414370031",
|
||||
"3": "0.0262780129766786",
|
||||
"4": "0.0278405849418856",
|
||||
"5": "0.0294960722713029",
|
||||
"6": "0.03125",
|
||||
"7": "0.033108221698728",
|
||||
"8": "0.0350769390096679",
|
||||
"9": "0.037162722343835",
|
||||
"10": "0.0393725328092148",
|
||||
"11": "0.0417137454428136",
|
||||
"12": "0.0441941738241592",
|
||||
"13": "0.0468220961523963",
|
||||
"14": "0.0496062828740062",
|
||||
"15": "0.0525560259533572",
|
||||
"16": "0.0556811698837712",
|
||||
"17": "0.0589921445426059",
|
||||
"18": "0.0625",
|
||||
"19": "0.066216443397456",
|
||||
"20": "0.0701538780193358",
|
||||
"21": "0.0743254446876701",
|
||||
"22": "0.0787450656184296",
|
||||
"23": "0.0834274908856271",
|
||||
"24": "0.0883883476483184",
|
||||
"25": "0.0936441923047926",
|
||||
"26": "0.0992125657480125",
|
||||
"27": "0.105112051906714",
|
||||
"28": "0.111362339767542",
|
||||
"29": "0.117984289085212",
|
||||
"30": "0.125",
|
||||
"31": "0.132432886794912",
|
||||
"32": "0.140307756038672",
|
||||
"33": "0.14865088937534",
|
||||
"34": "0.157490131236859",
|
||||
"35": "0.166854981771254",
|
||||
"36": "0.176776695296637",
|
||||
"37": "0.187288384609585",
|
||||
"38": "0.198425131496025",
|
||||
"39": "0.210224103813429",
|
||||
"40": "0.222724679535085",
|
||||
"41": "0.235968578170423",
|
||||
"42": "0.25",
|
||||
"43": "0.264865773589824",
|
||||
"44": "0.280615512077343",
|
||||
"45": "0.29730177875068",
|
||||
"46": "0.314980262473718",
|
||||
"47": "0.333709963542509",
|
||||
"48": "0.353553390593274",
|
||||
"49": "0.37457676921917",
|
||||
"50": "0.39685026299205",
|
||||
"51": "0.420448207626857",
|
||||
"52": "0.44544935907017",
|
||||
"53": "0.471937156340847",
|
||||
"54": "0.5",
|
||||
"55": "0.529731547179648",
|
||||
"56": "0.561231024154687",
|
||||
"57": "0.594603557501361",
|
||||
"58": "0.629960524947437",
|
||||
"59": "0.667419927085017",
|
||||
"60": "0.707106781186548",
|
||||
"61": "0.749153538438341",
|
||||
"62": "0.7937005259841",
|
||||
"63": "0.840896415253715",
|
||||
"64": "0.890898718140339",
|
||||
"65": "0.943874312681694",
|
||||
"66": "1",
|
||||
"67": "1.0594630943593",
|
||||
"68": "1.12246204830937",
|
||||
"69": "1.18920711500272",
|
||||
"70": "1.25992104989487",
|
||||
"71": "1.33483985417003",
|
||||
"72": "1.4142135623731",
|
||||
"73": "1.49830707687668",
|
||||
"74": "1.5874010519682",
|
||||
"75": "1.68179283050743",
|
||||
"76": "1.78179743628068",
|
||||
"77": "1.88774862536339",
|
||||
"78": "2",
|
||||
"79": "2.11892618871859",
|
||||
"80": "2.24492409661875",
|
||||
"81": "2.37841423000544",
|
||||
"82": "2.51984209978975",
|
||||
"83": "2.66967970834007",
|
||||
"84": "2.82842712474619",
|
||||
"85": "2.99661415375336",
|
||||
"86": "3.1748021039364",
|
||||
"87": "3.36358566101486",
|
||||
"88": "3.56359487256136",
|
||||
"89": "3.77549725072677",
|
||||
"90": "4",
|
||||
"91": "4.23785237743718",
|
||||
"92": "4.48984819323749",
|
||||
"93": "4.75682846001088",
|
||||
"94": "5.03968419957949",
|
||||
"95": "5.33935941668014",
|
||||
"96": "5.65685424949238",
|
||||
"97": "5.99322830750673",
|
||||
"98": "6.3496042078728",
|
||||
"99": "6.72717132202972",
|
||||
"100": "7.12718974512272",
|
||||
"101": "7.55099450145355",
|
||||
"102": "8",
|
||||
"103": "8.47570475487436",
|
||||
"104": "8.97969638647498",
|
||||
"105": "9.51365692002177",
|
||||
"106": "10.079368399159",
|
||||
"107": "10.6787188333603",
|
||||
"108": "11.3137084989848",
|
||||
"109": "11.9864566150135",
|
||||
"110": "12.6992084157456",
|
||||
"111": "13.4543426440594",
|
||||
"112": "14.2543794902454",
|
||||
"113": "15.1019890029071",
|
||||
"114": "16",
|
||||
"115": "16.9514095097487",
|
||||
"116": "17.95939277295",
|
||||
"117": "19.0273138400435",
|
||||
"118": "20.158736798318",
|
||||
"119": "21.3574376667206",
|
||||
"120": "22.6274169979695",
|
||||
"121": "23.9729132300269",
|
||||
"122": "25.3984168314912",
|
||||
"123": "26.9086852881189",
|
||||
"124": "28.5087589804909",
|
||||
"125": "30.2039780058142",
|
||||
"126": "32",
|
||||
"127": "33.9028190194974",
|
||||
"128": "35.9187855458999",
|
||||
"129": "38.0546276800871",
|
||||
"130": "40.3174735966359",
|
||||
"131": "42.7148753334411",
|
||||
}
|
||||
208
old-things/bgArrayLib/reader.py
Normal file
208
old-things/bgArrayLib/reader.py
Normal file
@@ -0,0 +1,208 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
# from nmcsup.log import log
|
||||
import pickle
|
||||
|
||||
|
||||
class Note:
|
||||
def __init__(self, channel, pitch, velocity, time, time_position, instrument):
|
||||
self.channel = channel
|
||||
self.pitch = pitch
|
||||
self.velocity = velocity
|
||||
self.delay = time
|
||||
self.time_position = time_position
|
||||
self.instrument = instrument
|
||||
self.CD = "d"
|
||||
|
||||
def get_CD(self, start, end):
|
||||
if end - start > 1.00:
|
||||
self.CD = "c"
|
||||
else:
|
||||
self.CD = "d"
|
||||
|
||||
|
||||
def midiNewReader(midfile: str):
|
||||
import mido
|
||||
|
||||
# from msctspt.threadOpera import NewThread
|
||||
from bgArrayLib.bpm import get
|
||||
|
||||
def Time(mt, tpb_a, bpm_a):
|
||||
return round(mt / tpb_a / bpm_a * 60 * 20)
|
||||
|
||||
Notes = []
|
||||
tracks = []
|
||||
note_list = []
|
||||
close = []
|
||||
on = []
|
||||
off = []
|
||||
instruments = []
|
||||
isPercussion = False
|
||||
try:
|
||||
mid = mido.MidiFile(midfile)
|
||||
except Exception:
|
||||
print("找不到文件或无法读取文件" + midfile)
|
||||
return False
|
||||
tpb = mid.ticks_per_beat
|
||||
bpm = get(mid)
|
||||
# 解析
|
||||
# def loadMidi(track1):
|
||||
for track in mid.tracks:
|
||||
overallTime = 0.0
|
||||
instrument = 0
|
||||
for i in track:
|
||||
overallTime += i.time
|
||||
try:
|
||||
if i.channel != 9:
|
||||
# try:
|
||||
# log("event_type(事件): " + str(i.type) + " channel(音轨): " + str(i.channel) +
|
||||
# " note/pitch(音高): " +
|
||||
# str(i[2]) +
|
||||
# " velocity(力度): " + str(i.velocity) + " time(间隔时间): " + str(i.time) +
|
||||
# " overallTime/globalTime/timePosition: " + str(overallTime) + " \n")
|
||||
# except AttributeError:
|
||||
# log("event_type(事件): " + str(i.type) + " thing(内容):" + str(i) + " \n")
|
||||
if "program_change" in str(i):
|
||||
instrument = i.program
|
||||
if instrument > 119: # 音色不够
|
||||
pass
|
||||
else:
|
||||
instruments.append(i.program)
|
||||
if "note_on" in str(i) and i.velocity > 0:
|
||||
print(i)
|
||||
# print(i.note)
|
||||
# print([Note(i.channel, i.note, i.velocity, i.time, Time(overallTime, tpb, bpm), instrument)])
|
||||
tracks.append(
|
||||
[
|
||||
Note(
|
||||
i.channel,
|
||||
i.note,
|
||||
i.velocity,
|
||||
i.time,
|
||||
Time(overallTime, tpb, bpm),
|
||||
instrument,
|
||||
)
|
||||
]
|
||||
)
|
||||
note_list.append(
|
||||
[
|
||||
i.channel,
|
||||
i.note,
|
||||
i.velocity,
|
||||
i.time,
|
||||
Time(overallTime, tpb, bpm),
|
||||
instrument,
|
||||
]
|
||||
)
|
||||
on.append([i.note, Time(overallTime, tpb, bpm)])
|
||||
# return [Note(i.channel, i, i.velocity, i.time, Time(overallTime, tpb, bpm))]
|
||||
if "note_off" in str(i) or "note_on" in str(i) and i.velocity == 0:
|
||||
# print(i)
|
||||
# print([Note(i.channel, i.note, i.velocity, i.time, Time(overallTime, tpb, bpm))])
|
||||
close.append(
|
||||
[
|
||||
Note(
|
||||
i.channel,
|
||||
i.note,
|
||||
i.velocity,
|
||||
i.time,
|
||||
Time(overallTime, tpb, bpm),
|
||||
instrument,
|
||||
)
|
||||
]
|
||||
)
|
||||
off.append([i.note, Time(overallTime, tpb, bpm)])
|
||||
# return [Note(i.channel, i, i.velocity, i.time, Time(overallTime, tpb, bpm))]
|
||||
except AttributeError:
|
||||
pass
|
||||
if "note_on" in str(i) and i.channel == 9:
|
||||
if "note_on" in str(i) and i.velocity > 0:
|
||||
print(i)
|
||||
# print(i.note)
|
||||
# print([Note(i.channel, i.note, i.velocity, i.time, Time(overallTime, tpb, bpm), -1)])
|
||||
tracks.append(
|
||||
[
|
||||
Note(
|
||||
i.channel,
|
||||
i.note,
|
||||
i.velocity,
|
||||
i.time,
|
||||
Time(overallTime, tpb, bpm),
|
||||
-1,
|
||||
)
|
||||
]
|
||||
)
|
||||
note_list.append(
|
||||
[
|
||||
i.channel,
|
||||
i.note,
|
||||
i.velocity,
|
||||
i.time,
|
||||
Time(overallTime, tpb, bpm),
|
||||
-1,
|
||||
]
|
||||
)
|
||||
on.append([i.note, Time(overallTime, tpb, bpm)])
|
||||
isPercussion = True
|
||||
# return [Note(i.channel, i, i.velocity, i.time, Time(overallTime, tpb, bpm))]
|
||||
Notes.append(tracks)
|
||||
if instruments is []:
|
||||
instruments.append(0)
|
||||
instruments = list(set(instruments))
|
||||
with open("1.pkl", "wb") as b:
|
||||
pickle.dump([instruments, isPercussion], b)
|
||||
|
||||
# for j, track in enumerate(mid.tracks):
|
||||
# th = NewThread(loadMidi, (track,))
|
||||
# th.start()
|
||||
# Notes.append(th.getResult())
|
||||
|
||||
# print(Notes)
|
||||
print(Notes.__len__())
|
||||
# print(note_list)
|
||||
print(instruments)
|
||||
return Notes
|
||||
# return [Notes, note_list]
|
||||
|
||||
|
||||
def midiClassReader(midfile: str):
|
||||
import mido
|
||||
|
||||
from bgArrayLib.bpm import get
|
||||
|
||||
def Time(mt, tpb_a, bpm_a):
|
||||
return round(mt / tpb_a / bpm_a * 60 * 20)
|
||||
|
||||
Notes = []
|
||||
tracks = []
|
||||
try:
|
||||
mid = mido.MidiFile(filename=midfile, clip=True)
|
||||
except Exception:
|
||||
print("找不到文件或无法读取文件" + midfile)
|
||||
return False
|
||||
print("midi已经载入了。")
|
||||
tpb = mid.ticks_per_beat
|
||||
bpm = get(mid)
|
||||
for track in mid.tracks:
|
||||
overallTime = 0.0
|
||||
instrument = 0
|
||||
for i in track:
|
||||
overallTime += i.time
|
||||
if "note_on" in str(i) and i.velocity > 0:
|
||||
print(i)
|
||||
tracks.append(
|
||||
[
|
||||
Note(
|
||||
i.channel,
|
||||
i.note,
|
||||
i.velocity,
|
||||
i.time,
|
||||
Time(overallTime, tpb, bpm),
|
||||
instrument,
|
||||
)
|
||||
]
|
||||
)
|
||||
Notes.append(tracks)
|
||||
print(Notes.__len__())
|
||||
return Notes
|
||||
147
old-things/bgArrayLib/sy_resourcesPacker.py
Normal file
147
old-things/bgArrayLib/sy_resourcesPacker.py
Normal file
@@ -0,0 +1,147 @@
|
||||
import os
|
||||
import pickle
|
||||
import shutil
|
||||
|
||||
# import tkinter.filedialog
|
||||
# from namesConstant import zip_name
|
||||
# from namesConstant import mcpack_name
|
||||
import bgArrayLib.namesConstant
|
||||
|
||||
zipN = bgArrayLib.namesConstant.zip_name
|
||||
mpN = bgArrayLib.namesConstant.mcpack_name
|
||||
|
||||
manifest = {
|
||||
"format_version": 1,
|
||||
"header": {
|
||||
"name": "羽音缭绕-midiout_25.5--音创使用",
|
||||
"description": "羽音缭绕-midiout_25.0--音创使用",
|
||||
"uuid": "c1adbda4-3b3e-4e5b-a57e-cde8ac80ee19",
|
||||
"version": [25, 5, 0],
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"description": "羽音缭绕-midiout_25.0--音创使用",
|
||||
"type": "resources",
|
||||
"uuid": "c13455d5-b9f3-47f2-9706-c05ad86b3180 ",
|
||||
"version": [25, 5, 0],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def resources_pathSetting(newPath: str = ""):
|
||||
if not os.path.isfile("./bgArrayLib/resourcesPath.rpposi") and newPath == "":
|
||||
return [False, 1] # 1:没有路径文件
|
||||
elif newPath != "": # not os.path.isfile("./bgArrayLib/resourcesPath.rpposi") and
|
||||
path = newPath
|
||||
print(path)
|
||||
with open("./bgArrayLib/resourcesPath.rpposi", "w") as w:
|
||||
w.write(path)
|
||||
if "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||
path
|
||||
) and "zip格式_25.0" in os.listdir(path):
|
||||
return [True, path, 1] # 1:都有
|
||||
elif "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||
path
|
||||
) and "zip格式_25.0" not in os.listdir(path):
|
||||
return [True, path, 2] # 2:有pack
|
||||
elif "mcpack(国际版推荐)格式_25.0" not in os.listdir(
|
||||
path
|
||||
) and "zip格式_25.0" in os.listdir(path):
|
||||
return [True, path, 3] # 3:有zip
|
||||
else:
|
||||
return [False, 2] # 2:路径文件指示错误
|
||||
if os.path.isfile("./bgArrayLib/resourcesPath.rpposi") and newPath == "":
|
||||
with open("./bgArrayLib/resourcesPath.rpposi", "r") as f:
|
||||
path = f.read()
|
||||
if "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||
path
|
||||
) and "zip格式_25.0" in os.listdir(path):
|
||||
return [True, path, 1] # 1:都有
|
||||
elif "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||
path
|
||||
) and "zip格式_25.0" not in os.listdir(path):
|
||||
return [True, path, 2] # 2:有pack
|
||||
elif "mcpack(国际版推荐)格式_25.0" not in os.listdir(
|
||||
path
|
||||
) and "zip格式_25.0" in os.listdir(path):
|
||||
return [True, path, 3] # 3:有zip
|
||||
else:
|
||||
return [False, 2] # 2:路径文件指示错误
|
||||
|
||||
raise
|
||||
|
||||
|
||||
def choose_resources():
|
||||
global zipN
|
||||
global mpN
|
||||
back_list = []
|
||||
try:
|
||||
with open(r"1.pkl", "rb") as rb:
|
||||
instrument = list(pickle.load(rb))
|
||||
print(instrument)
|
||||
except FileNotFoundError:
|
||||
with open(r"./nmcsup/1.pkl", "rb") as rb:
|
||||
instrument = list(pickle.load(rb))
|
||||
print(instrument)
|
||||
path = resources_pathSetting()
|
||||
if path.__len__() == 2:
|
||||
return path
|
||||
else:
|
||||
dataT = path[2]
|
||||
pathT = path[1]
|
||||
if dataT == 1:
|
||||
if instrument[1] is True: # 是否存在打击乐器
|
||||
index = zipN.get(-1, "")
|
||||
percussion_instrument = str(pathT) + "\\zip格式_25.0\\" + index
|
||||
# print(percussion_instrument)
|
||||
back_list.append(percussion_instrument)
|
||||
for i in instrument[0]:
|
||||
ins_p = str(pathT) + "\\zip格式_25.0\\" + str(zipN.get(i))
|
||||
# print(ins_p)
|
||||
back_list.append(ins_p)
|
||||
print(back_list)
|
||||
return back_list
|
||||
elif dataT == 2:
|
||||
if instrument[1] is True:
|
||||
index = mpN.get(-1, "")
|
||||
percussion_instrument = (
|
||||
str(pathT) + "\\mcpack(国际版推荐)格式_25.0\\" + index
|
||||
)
|
||||
# print(percussion_instrument)
|
||||
back_list.append(percussion_instrument)
|
||||
for i in instrument[0]:
|
||||
ins_p = str(pathT) + "\\mcpack(国际版推荐)格式_25.0\\" + str(mpN.get(i))
|
||||
# print(ins_p)
|
||||
back_list.append(ins_p)
|
||||
print(back_list)
|
||||
return back_list
|
||||
elif dataT == 3:
|
||||
if instrument[1] is True:
|
||||
index = zipN.get(-1, "")
|
||||
percussion_instrument = str(pathT) + "\\zip格式_25.0\\" + index
|
||||
# print(percussion_instrument)
|
||||
back_list.append(percussion_instrument)
|
||||
for i in instrument[0]:
|
||||
ins_p = str(pathT) + "\\zip格式_25.0\\" + str(zipN.get(i))
|
||||
# print(ins_p)
|
||||
back_list.append(ins_p)
|
||||
print(back_list)
|
||||
return back_list
|
||||
raise
|
||||
|
||||
|
||||
def scatteredPack(path):
|
||||
pack_list = choose_resources()
|
||||
print(pack_list)
|
||||
print(path)
|
||||
# os.close("L:/0WorldMusicCreater-MFMS new edition")
|
||||
# shutil.copy("L:\\shenyu\\音源的资源包\\羽音缭绕-midiout_25.0\\mcpack(国际版推荐)格式_25.0\\0.Acoustic_Grand_Piano_大钢琴.mcpack",
|
||||
# "L:/0WorldMusicCreater-MFMS new edition")
|
||||
for i in pack_list:
|
||||
shutil.copy(i, path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# print(resources_pathSetting(r"L:\shenyu\音源的资源包\羽音缭绕-midiout_25.0"))
|
||||
choose_resources()
|
||||
@@ -9,8 +9,8 @@
|
||||
Musicreater (音·创)
|
||||
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
|
||||
|
||||
版权所有 © 2024 音·创 开发者
|
||||
Copyright © 2024 all the developers of Musicreater
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 ./License.md
|
||||
Terms & Conditions: ./License.md
|
||||
@@ -18,20 +18,19 @@ Terms & Conditions: ./License.md
|
||||
|
||||
import os
|
||||
|
||||
import Musicreater
|
||||
from Musicreater.plugin import ConvertConfig
|
||||
from Musicreater.plugin.addonpack import (
|
||||
import Musicreater.old_init as old_init
|
||||
from Musicreater.old_plugin.addonpack import (
|
||||
to_addon_pack_in_delay,
|
||||
to_addon_pack_in_repeater,
|
||||
to_addon_pack_in_score,
|
||||
)
|
||||
from Musicreater.plugin.mcstructfile import (
|
||||
from Musicreater.old_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
|
||||
from Musicreater.old_plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||
|
||||
# 获取midi列表
|
||||
midi_path = input(f"请输入MIDI路径:")
|
||||
@@ -80,11 +79,11 @@ else:
|
||||
# 提示语 检测函数 错误提示语
|
||||
for args in [
|
||||
(
|
||||
f"输入音量:",
|
||||
f"最小播放音量:",
|
||||
float,
|
||||
),
|
||||
(
|
||||
f"输入播放速度:",
|
||||
f"播放速度:",
|
||||
float,
|
||||
),
|
||||
(
|
||||
@@ -118,21 +117,19 @@ else:
|
||||
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 ()
|
||||
),
|
||||
"结构延展方向:",
|
||||
lambda a: isin(
|
||||
a,
|
||||
{
|
||||
"z+": ["z+", "Z+"],
|
||||
"x+": ["X+", "x+"],
|
||||
"z-": ["Z-", "z-"],
|
||||
"x-": ["x-", "X-"],
|
||||
},
|
||||
),
|
||||
)
|
||||
if (playerFormat == 2 and fileFormat == 2)
|
||||
else ()
|
||||
)
|
||||
),
|
||||
(
|
||||
@@ -152,12 +149,17 @@ else:
|
||||
),
|
||||
]:
|
||||
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 = old_init.MidiConvert.from_midi_file(
|
||||
midi_path, old_exe_format=False, min_volume=prompts[0], play_speed=prompts[1]
|
||||
)
|
||||
|
||||
|
||||
if fileFormat == 0:
|
||||
if playerFormat == 1:
|
||||
@@ -174,26 +176,62 @@ elif fileFormat == 2:
|
||||
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,
|
||||
old_init.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:])
|
||||
to_BDX_file_in_score(
|
||||
cvt_mid,
|
||||
out_path,
|
||||
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||
*prompts[3:],
|
||||
)
|
||||
if playerFormat == 1
|
||||
else to_BDX_file_in_delay(cvt_mid, cvt_cfg, *prompts[3:])
|
||||
else to_BDX_file_in_delay(
|
||||
cvt_mid,
|
||||
out_path,
|
||||
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||
*prompts[3:],
|
||||
)
|
||||
)
|
||||
)
|
||||
if fileFormat == 1
|
||||
else (" 结构大小:{},延迟总数:{},指令数量:{}".format(
|
||||
*(cvt_method(cvt_mid, cvt_cfg, *prompts[3:])) # type: ignore
|
||||
) if playerFormat == 2 else
|
||||
" 结构大小:{},延迟总数:{}".format(
|
||||
*(cvt_method(cvt_mid, cvt_cfg, *prompts[3:])) # type: ignore
|
||||
else (
|
||||
" 结构大小:{},延迟总数:{},指令数量:{}".format(
|
||||
*(
|
||||
cvt_method(
|
||||
cvt_mid,
|
||||
out_path,
|
||||
*prompts[3:],
|
||||
)
|
||||
)
|
||||
)
|
||||
if playerFormat == 1
|
||||
else " 结构大小:{},延迟总数:{}".format(
|
||||
*(
|
||||
cvt_method(
|
||||
cvt_mid,
|
||||
out_path,
|
||||
# Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
|
||||
*prompts[3:],
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
14
old-things/example_futureFunction.py
Normal file
14
old-things/example_futureFunction.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import Musicreater.experiment
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.mcstructfile
|
||||
|
||||
print(
|
||||
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
|
||||
input("midi路径:"), old_exe_format=False
|
||||
),
|
||||
input("输出路径:"),
|
||||
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||
max_height=32,
|
||||
)
|
||||
)
|
||||
16
old-things/example_singleConvert.py
Normal file
16
old-things/example_singleConvert.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import Musicreater.old_init as old_init
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.mcstructfile
|
||||
|
||||
print(
|
||||
old_init.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
old_init.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
old-things/example_websocket.py
Normal file
24
old-things/example_websocket.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import Musicreater.old_init as old_init
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.websocket
|
||||
|
||||
import os
|
||||
|
||||
dire = input("midi目录:")
|
||||
|
||||
print(
|
||||
old_init.old_plugin.websocket.to_websocket_server(
|
||||
[
|
||||
old_init.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("服务器端口:")),
|
||||
old_init.DEFAULT_PROGRESSBAR_STYLE,
|
||||
)
|
||||
)
|
||||
1
old-things/fcwslib/LICENSE
Normal file
1
old-things/fcwslib/LICENSE
Normal file
@@ -0,0 +1 @@
|
||||
SEE: https://mingfengpigeon.mit-license.org/
|
||||
5
old-things/fcwslib/__init__.py
Normal file
5
old-things/fcwslib/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
__all__ = ['Server', 'Plugin', 'build_header']
|
||||
__version__ = '3.0.1'
|
||||
__author__ = ['mingfengpigeon <mingfengpigeon@gmail.com>',"Eilles Wan <EillesWan@outlook.com>"]
|
||||
|
||||
from .server import Server, Plugin, build_header
|
||||
142
old-things/fcwslib/server.py
Normal file
142
old-things/fcwslib/server.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import asyncio
|
||||
import copy
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import websockets
|
||||
|
||||
|
||||
class Server:
|
||||
sent_commands = {}
|
||||
subscribed_events = {}
|
||||
_plugins = []
|
||||
_connections = []
|
||||
|
||||
def __init__(self, server='0.0.0.0', port=8000, debug_mode=False):
|
||||
self._server = server
|
||||
self._port = port
|
||||
self._debug_mode = debug_mode
|
||||
|
||||
def handler(self):
|
||||
return copy.deepcopy(self._plugins)
|
||||
|
||||
def add_plugin(self, plugin):
|
||||
if self._plugins:
|
||||
for connection in self._connections:
|
||||
plugin_ = plugin()
|
||||
asyncio.create_task(plugin_.on_connect())
|
||||
connection.append(plugin_)
|
||||
self._plugins.append(plugin)
|
||||
|
||||
def remove_plugin(self, plugin):
|
||||
if self._connections:
|
||||
for connection in self._connections:
|
||||
for plugin_ in connection.plugins:
|
||||
if isinstance(plugin_, plugin):
|
||||
plugin_.remove(plugin_)
|
||||
break
|
||||
self._plugins.remove(plugin)
|
||||
|
||||
async def run_forever(self):
|
||||
self.running = True
|
||||
async with websockets.serve(self._on_connect, self._server, self._port):
|
||||
await asyncio.Future()
|
||||
|
||||
async def _on_connect(self, websocket, path):
|
||||
plugins = []
|
||||
self._connections.append({
|
||||
"websocket": websocket,
|
||||
"path": path,
|
||||
"plugins": plugins,
|
||||
})
|
||||
for plugin in self._plugins:
|
||||
plugins.append(plugin(websocket, path, self, self._debug_mode))
|
||||
for plugin in plugins:
|
||||
asyncio.create_task(plugin.on_connect())
|
||||
while self.running:
|
||||
try:
|
||||
response = json.loads(await websocket.recv())
|
||||
except (websockets.exceptions.ConnectionClosedOK, websockets.exceptions.ConnectionClosedError):
|
||||
tasks = []
|
||||
for plugin in plugins:
|
||||
tasks.append(plugin.on_disconnect())
|
||||
for task in tasks:
|
||||
await task
|
||||
break
|
||||
else:
|
||||
message_purpose = response['header']['messagePurpose']
|
||||
if message_purpose == 'commandResponse':
|
||||
request_id = response['header']['requestId']
|
||||
if request_id in self.sent_commands:
|
||||
asyncio.create_task(self.sent_commands[request_id](response))
|
||||
del self.sent_commands[request_id]
|
||||
else:
|
||||
try:
|
||||
event_name = response['header']['eventName']
|
||||
asyncio.create_task(self.subscribed_events[event_name](response))
|
||||
except KeyError:
|
||||
print("ERROR EVENT NAME:\n{}".format(response))
|
||||
|
||||
async def disconnect(self, websocket: websockets.WebSocketServerProtocol):
|
||||
self.running = False
|
||||
await websocket.close_connection()
|
||||
for number in range(len(self._connections) - 1):
|
||||
connection = self._connections[number]
|
||||
if connection['websocket'] == websocket:
|
||||
del self._connections[number]
|
||||
|
||||
|
||||
class Plugin:
|
||||
def __init__(self, websocket, path, server, debug_mode=False):
|
||||
self._websocket = websocket
|
||||
self._path = path
|
||||
self._server = server
|
||||
self._debug_mode = debug_mode
|
||||
|
||||
async def on_connect(self):
|
||||
pass
|
||||
|
||||
async def on_disconnect(self):
|
||||
pass
|
||||
|
||||
async def on_receive(self, response):
|
||||
pass
|
||||
|
||||
async def send_command(self, command, callback=None):
|
||||
request = {
|
||||
'body': {'commandLine': command},
|
||||
'header': build_header('commandRequest')
|
||||
}
|
||||
if callback:
|
||||
self._server.sent_commands[request['header']['requestId']] = callback
|
||||
await self._websocket.send(json.dumps(request, **{'indent': 4} if self._debug_mode else {}))
|
||||
|
||||
async def subscribe(self, event_name, callback):
|
||||
request = {
|
||||
'body': {'eventName': event_name},
|
||||
'header': build_header('subscribe')
|
||||
}
|
||||
self._server.subscribed_events[event_name] = callback
|
||||
await self._websocket.send(json.dumps(request, **{'indent': 4} if self._debug_mode else {}))
|
||||
|
||||
async def unsubscribe(self, event_name):
|
||||
request = {
|
||||
'body': {'eventName': event_name},
|
||||
'header': build_header('unsubscribe')
|
||||
}
|
||||
del self._server.subscribed_events[event_name]
|
||||
await self._websocket.send(json.dumps(request, **{'indent': 4} if self._debug_mode else {}))
|
||||
|
||||
async def disconnect(self):
|
||||
await self._server.disconnect(self._websocket)
|
||||
|
||||
|
||||
def build_header(message_purpose, request_id=None):
|
||||
if not request_id:
|
||||
request_id = str(uuid.uuid4())
|
||||
return {
|
||||
'requestId': request_id,
|
||||
'messagePurpose': message_purpose,
|
||||
'version': '1',
|
||||
'messageType': 'commandRequest',
|
||||
}
|
||||
163
old-things/let_future_java.py
Normal file
163
old-things/let_future_java.py
Normal 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.old_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.old_init.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),
|
||||
)
|
||||
39
old-things/test_fsq_opera.py
Normal file
39
old-things/test_fsq_opera.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from rich.pretty import pprint
|
||||
|
||||
import Musicreater.old_init as old_init
|
||||
from Musicreater.utils import (
|
||||
load_decode_fsq_flush_release,
|
||||
load_decode_musicsequence_metainfo,
|
||||
)
|
||||
|
||||
msc_seq = old_init.MusicSequence.from_mido(
|
||||
old_init.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 = old_init.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
old-things/test_future_kamires.py
Normal file
33
old-things/test_future_kamires.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import Musicreater.experiment
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_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.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
msct,
|
||||
opt,
|
||||
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||
max_height=32,
|
||||
),
|
||||
)
|
||||
33
old-things/test_future_lyric.py
Normal file
33
old-things/test_future_lyric.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import Musicreater.experiment
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_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.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
msct,
|
||||
opt,
|
||||
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||
max_height=32,
|
||||
),
|
||||
)
|
||||
34
old-things/test_msq_opera.py
Normal file
34
old-things/test_msq_opera.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from rich.pretty import pprint
|
||||
|
||||
import Musicreater.old_init as old_init
|
||||
from Musicreater.utils import (
|
||||
load_decode_msq_flush_release,
|
||||
load_decode_musicsequence_metainfo,
|
||||
)
|
||||
|
||||
msc_seq = old_init.MusicSequence.from_mido(
|
||||
old_init.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 = old_init.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)
|
||||
101
pyproject.toml
Normal file
101
pyproject.toml
Normal file
@@ -0,0 +1,101 @@
|
||||
[project]
|
||||
name = "Musicreater"
|
||||
dynamic = ["version"]
|
||||
requires-python = ">= 3.8, < 4.0"
|
||||
dependencies = [
|
||||
"mido >= 1.3",
|
||||
"tomli >= 2.4.0; python_version < '3.11'",
|
||||
"tomli-w >= 1.0.0",
|
||||
"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",
|
||||
"numpy",
|
||||
]
|
||||
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"
|
||||
@@ -1 +0,0 @@
|
||||
mido>=1.3
|
||||
@@ -1,21 +0,0 @@
|
||||
# 注意,这里是作者署名文件,文件格式开头为单子启
|
||||
# 紧跟其后,不加空格留下常用名,常用名即常用网名
|
||||
# 而在其后是各个语言下的名字。用 井字符 开头表示
|
||||
# 注释,请注意,注释符号必须在一行之首否则无作用
|
||||
# 每进行一次分段表示一个新的开发者,换行表示一个
|
||||
# 新的语言。请全体开发者就此署名,谢谢!
|
||||
启金羿
|
||||
zh-CN 金羿
|
||||
zh-TW 金羿
|
||||
zh-ME 金羿羿喵
|
||||
zh-HK 金 羿
|
||||
en-GB Eilles Wan
|
||||
en-US EillesWan
|
||||
|
||||
启诸葛亮与八卦阵
|
||||
zh-CN 诸葛亮与八卦阵
|
||||
zh-TW 諸葛亮與八卦陣
|
||||
zh-ME 诸葛八卦喵
|
||||
zh-HK 諸葛亮與八卦陣
|
||||
en-GB Bagua Array
|
||||
en-US bgArray
|
||||
@@ -1,47 +0,0 @@
|
||||
> 是谁把科技的领域布满政治的火药
|
||||
>
|
||||
> 是谁把纯净的蓝天染上暗淡的沉灰
|
||||
>
|
||||
> 中国人民无不热爱自己伟大的祖国
|
||||
>
|
||||
> 我们不会忘记屈辱历史留下的惨痛
|
||||
>
|
||||
> 我们希望世界和平
|
||||
>
|
||||
> 我们希望获得世界的尊重
|
||||
>
|
||||
> 愿世上再也没有战争
|
||||
>
|
||||
> 无论是热还是冷
|
||||
>
|
||||
> 无论是经济还是政治
|
||||
>
|
||||
> 让美妙的和平的优雅的音乐响彻世界
|
||||
>
|
||||
> ——金羿
|
||||
> 2022 5 7
|
||||
|
||||
|
||||
|
||||
> Who has dropped political gunpowder into the technology
|
||||
>
|
||||
> Who has dyed clear blue sky into the dark grey
|
||||
>
|
||||
> All Chinese people love our great homeland
|
||||
>
|
||||
> We *WILL* remember the remain pain of the humiliating history
|
||||
>
|
||||
> We love the whole world but in peace
|
||||
>
|
||||
> We love everyone but under respect
|
||||
>
|
||||
> It is to be hoped that the war ends forever
|
||||
>
|
||||
> Whatever it is cold or hot
|
||||
>
|
||||
> Whatever it is economical or political
|
||||
>
|
||||
> Just let the wonderful music of peace surround the world
|
||||
>
|
||||
> ---- Eilles Wan
|
||||
> 7/5 2022
|
||||
260
resources/test/enfasted_list_merging_test.py
Normal file
260
resources/test/enfasted_list_merging_test.py
Normal 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)
|
||||
21
resources/test/genexpr_vs_yieldfrom.py
Normal file
21
resources/test/genexpr_vs_yieldfrom.py
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
# 模拟两种写法
|
||||
def method_A(self, start, end):
|
||||
yield from (f"{track}.get_range(start, end)" for track in self)
|
||||
|
||||
def method_B(self, start, end):
|
||||
return (f"{track}.get_range(start, end)" for track in self)
|
||||
|
||||
|
||||
|
||||
tracks = ["A", "B"]
|
||||
|
||||
gen_a = method_A(tracks, 0, 10)
|
||||
print(list(gen_a))
|
||||
|
||||
|
||||
|
||||
gen_b = method_B(tracks, 0, 10)
|
||||
print(list(gen_b))
|
||||
|
||||
# they are the same output
|
||||
39
resources/test/list_merging_text.py
Normal file
39
resources/test/list_merging_text.py
Normal 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("结束")
|
||||
@@ -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,):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
不得用于商业用途
|
||||
若 音·创 库被用于商业用途,应当将其剔除
|
||||
|
||||
版权所有 © 2024 诸葛亮与八卦阵
|
||||
版权所有 © 2026 玉衡Alioth
|
||||
38
setup.py
38
setup.py
@@ -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,
|
||||
)
|
||||
24
test_read.py
Normal file
24
test_read.py
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
# 一个简单的项目实践测试
|
||||
from pathlib import Path
|
||||
from Musicreater import load_plugin_module, MusiCreater
|
||||
from Musicreater.plugins import _global_plugin_registry
|
||||
|
||||
load_plugin_module("Musicreater.builtin_plugins.midi_read")
|
||||
|
||||
print("当前支持的导入格式:", _global_plugin_registry.supported_input_formats())
|
||||
print("当前支持的导出格式:", _global_plugin_registry.supported_output_formats())
|
||||
|
||||
print(msct:=MusiCreater.import_music(Path("./resources/测试片段.mid")))
|
||||
|
||||
print(msct.music)
|
||||
|
||||
|
||||
# 为了让类型检查器满意,以下方法不建议使用,因为这本质上是越过了 MusiCreater 类而直接执行插件的函数
|
||||
print(t := msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), None))
|
||||
# 我们建议用这种方式来代替
|
||||
t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(Path("./resources/测试片段.mid"), None)
|
||||
|
||||
print(_global_plugin_registry)
|
||||
print(msct._plugin_cache)
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user