mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-27 19:55:42 +00:00
Compare commits
1 Commits
v3.0.0-alp
...
beewarewin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acf4c6907e |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,3 +0,0 @@
|
||||
*.yaml linguist-language=Python
|
||||
*.xml linguist-language=Python
|
||||
*.md linguist-language=Python
|
||||
199
.gitignore
vendored
199
.gitignore
vendored
@@ -1,40 +1,38 @@
|
||||
# sth. can't open
|
||||
/msctPkgver/secrets/*.py
|
||||
/msctPkgver/secrets/*.c
|
||||
/fool/
|
||||
|
||||
# mystuff
|
||||
/*.zip
|
||||
/.vscode
|
||||
/*.mid
|
||||
/*.midi
|
||||
/*.mcpack
|
||||
/*.bdx
|
||||
/*.msq
|
||||
/*.fsq
|
||||
/*.json
|
||||
/*.mcstructure
|
||||
.mscbackup
|
||||
/logs
|
||||
/languages
|
||||
/llc_cli.py
|
||||
/utils
|
||||
test.py
|
||||
RES.txt
|
||||
/MSCT_Packer.py
|
||||
/Packer/*.MPK
|
||||
/Packer/checksum.txt
|
||||
/bgArrayLib
|
||||
/fcwslib
|
||||
test_lyric-mido.py
|
||||
|
||||
# Byte-compiled / optimized
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# OSX useful to ignore
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
@@ -46,130 +44,23 @@ lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
.pdm-build/
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
# IntelliJ Idea family of suites
|
||||
.idea
|
||||
*.iml
|
||||
## File-based project format:
|
||||
*.ipr
|
||||
*.iws
|
||||
## mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# Pycharm
|
||||
/.idea
|
||||
|
||||
# log
|
||||
/.log
|
||||
|
||||
# package
|
||||
.7z
|
||||
# Briefcase build directories
|
||||
iOS/
|
||||
macOS/
|
||||
windows/
|
||||
android/
|
||||
linux/
|
||||
django/
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
3.10
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"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.
|
||||
|
||||
"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.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"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.
|
||||
|
||||
"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).
|
||||
|
||||
"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.
|
||||
|
||||
"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."
|
||||
|
||||
"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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(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
|
||||
|
||||
(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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
123
LICENSE.md
123
LICENSE.md
@@ -1,123 +0,0 @@
|
||||
# 汉钰律许可协议,第一版
|
||||
|
||||
**总第一版 第二次修订 · 二〇二四年七月七日编 二〇二五年四月二十六日修订**
|
||||
|
||||
## 一、重要须知
|
||||
|
||||
1. 为保护采用本协议的作品在开源过程中,其著作权人所应有的权益,根据**中华人民共和国著作权法和相关法律法规**,制定本协议。
|
||||
|
||||
2. 本协议履行过程中,请注意本协议中**免除或限制**民事主体**责任或权利**的条款、法律适用和争议解决条款(尤其是加有特殊标记的条款),这些条款应在中国法律所允许的范围内最大程度地适用。
|
||||
|
||||
3. 若本协议所涉及的自然人**未满 18 周岁**,该自然人应在监护人的陪同下阅读本协议及有关本协议的条款内容,并在取得其监护人同意后开始或继续应用本协议所授权的行为。
|
||||
|
||||
4. 由于互联网服务、互联网内容的特殊性,若本协议以电子协议形式分发并签订,其依然有效。您一旦开始对本协议所授权之作品进行本协议所授权的行为,即视为您已经阅读、理解并同意并已经接受本协议的全部条款。
|
||||
|
||||
5. 本协议的订立、履行、解释及争议的解决均**适用中华人民共和国法律并排除其他一切冲突法的适用**。_本协议订立于许可证最初的颁发者的地址。若颁发者为自然人,则订立于该自然人户籍所在地;若为法人或非法人组织,则订立于其注册地_。本协议的订立各方应友好协商解决于协议所规定之行为的履行相关的争议;如协商不成,任何一方均可向合同签订地有管辖权的人民法院提起诉讼。
|
||||
|
||||
6. 本协议的原本仅为现代汉语,书写于简体中文。若存在其他语言的翻译或其他同等语言但非简体中文文本的版本,应当无法律效力。
|
||||
|
||||
## 二、术语定义
|
||||
|
||||
1. “**许可证**”、“**协议**”(后文称“本协议”)是指根据本文档中所列举的全部术语、定义、条款、限制等文本,是本合同的简称称谓。本合同全称是 **“汉钰律许可协议,第一版”**。
|
||||
|
||||
2. “**协议颁发者**”(后文称“颁发者”)是将条款或协议应用于其拥有著作财产权的作品的民事主体,或由其指定从而拥有颁发者身份的民事主体。
|
||||
|
||||
3. “**源**”形式是指对包括但不限于 软件、硬件、文档、配置项 等种类的作品进行修改、编辑的首选形式;若不存在首选形式,则初次编辑该作品所需的形式即为源形式。
|
||||
|
||||
4. “**目标**”形式是指对源形式进行机械转换、翻译、打印、制造、加工等同类型活动后形成的结果形式,包括但不限于源代码编译后的目标软件、生成的文件、转换出的媒体、制造出的机械、打印出的实体文本、加工后的零件。
|
||||
|
||||
5. “**采用本协议的作品**”(后文称“此作品”)是指经颁发者授权而使用本协议进行授权的任何作品,该作品应在自然人可见处明确附加一个自然人可读的版权通知(可以参考文末附录中提供的示例);若在一个可分割的作品中,部分地采用本协议进行授权,则该部分应当视为一个独立的采用本协议的作品,该作品应当在自然人可见处明确附加一个自然人可读的范围限定和版权通知(同样可以参考文末附录中提供的示例)。
|
||||
|
||||
6. “**贡献**”是指对作品进行的,意在提交给此作品颁发者以让著作权人包含在其作品中的任何修订或补充,该修订或补充同样属于一种作品。依据此定义,**“提交”**一词表示经由此作品颁发者所指定的形式,将其所进行的修改发送给此作品颁发者。该形式应当包括在此作品颁发者指定的平台内发送易于编辑的修改信息、在此作品颁发者指定的电子邮箱中发送易于编辑的修改信息、在此作品颁发者指定的源码控制系统或发布跟踪系统上提交的易于编辑的修改信息,但由著作权人以明显标注或指定为“非贡献”的活动除外。颁发者自己对作品进行的修改同样视作对作品的贡献。
|
||||
|
||||
7. “**贡献者**”是指此作品颁发者接受的贡献的提交者,或包含在作品的贡献清单中的民事主体。贡献者在提交贡献并经此作品颁发者通过且该贡献已经被应用于此作品中后,该贡献者应当视为此作品的著作权人之一,但不应视为此作品非其贡献的部分的著作权人。一个作品的颁发者同样属于其贡献者。**请注意**,针对贡献者提交的贡献,该贡献者应被视为该贡献的协议颁发者,但不应视作本作品的颁发者。
|
||||
|
||||
8. “**用户**”、“**使用者**”是指行使本协议所授权之行为的民事主体。据此,贡献者亦属于用户。
|
||||
|
||||
9. “**商业性使用**”、“**商用**”是指任何以谋取利益为目的的使用,包括但不限于以贩卖、出租的形式对作品进行使用;但若将该获取利益之活动明确指示为“捐赠”,且在获利者在进行本协议所授权的活动时不以捐赠数额为标准而区别之,则此种的获取利益的“捐赠”行为不属于商业性使用。
|
||||
|
||||
## 三、权利授予
|
||||
|
||||
1. 任何由颁发者所进行的特殊声明、特别注意等此类内容,应当在法律效力上高于本协议的条款或声明;这些声明若与本协议冲突,本协议的该冲突部分无效;本协议与这些声明共同构成颁发者与用户之间的合同。
|
||||
|
||||
2. 此作品的贡献者享有其贡献的完整著作权。
|
||||
|
||||
3. 此作品的贡献者将自己的贡献的全部著作财产权,免费、公开、不可撤销、无限期、非专有地授予此作品的全部著作权人,并准许其在全世界范围内使用上述权利;若无明确的标识,贡献者允许此作品的颁发者对其贡献进行免费、公开、不可撤销、无限期、非专有、世界范围内的商业性使用。
|
||||
|
||||
4. 此作品的著作权人及贡献者授予用户**免费、公开、不可撤销、非专有、非商用**地以任意形式**复制、发行、展览、表演、放映、广播、信息网络传播、摄制、改编、翻译、汇编、二次授权**的权利,准许其在此作品颁发者所指定的区域与时间内行使上述权利;若此作品颁发者未特别指定的,则视作在全世界范围内无限期地授权;若此作品颁发者特别指定在特定情况下可以商用,则应当按照其所指定的条件进行商业性使用,商用的过程中,应当明确标识此作品的著作权人。
|
||||
|
||||
5. 一旦此作品有任意由非贡献形式而产生的更改,更改的部分将不视为此作品的一部分,除非该部分不可离开此作品单独存在;若该部分必须依赖此作品而不可与此作品分离从而单独存在,则更改后的作品不视作此作品,在这种情况下,除非此更改后的作品已获得此作品颁发者的特殊许可、或更改者即为此作品颁发者本人,否则对该作品进行的任何活动都应当遵守本协议。
|
||||
|
||||
6. 经贡献而产生的对此作品的更改,属于此作品的一部分;在此情况下,更改后的作品,依旧视作此作品。
|
||||
|
||||
7. 依据本款的第 4 条,若用户在本协议的授权下,将此作品授予他人进行任何形式的活动(即“二次授权”、“二次分发”),则应确保其使用的协议或授权内容,与本协议的条款不冲突;当存在与本协议条款的冲突时,则该冲突内容无效,被授权的第三方应依照本协议的条款进行活动;除非该用户获得了此作品颁发者的特殊许可、或该用户即为此作品颁发者本人。
|
||||
|
||||
8. 依据本款的第 5 条,若由非贡献形式而产生更改的部分是可分割而不需依赖此作品即可单独存在的,若该部分明确注明不使用本协议进行授权或明确声明了其他授权条款,则该部分不视作采用本协议;但未更改的部分仍应视作原此作品的一部分,需要采用本协议进行授权,除非此更改后的作品已获得此作品颁发者的特殊许可、或更改者即为此作品颁发者本人。
|
||||
|
||||
9. 若此作品或所提交的贡献包含其著作权人的专利,则该专利所有人即此作品的著作权人应准许此作品全体著作权人**免费、公开、不可撤销、非专有、无版权费的专利许可**,以便贡献者对作品进行本协议所授权进行的活动。
|
||||
|
||||
10. 上述专利许可的授予,仅适用于在所提交的贡献中,可由专利所有者授予的,且在对此作品进行本协议所授权的活动中,必须使用的专利。
|
||||
|
||||
11. 如果用户对任何民事主体,因其在进行本协议所授权进行的活动中侵犯该用户的专利而提起诉讼,那么根据本协议授予该用户的所有关于此作品的任何其他专利许可将在提起上述诉讼之日起终止。
|
||||
|
||||
12. 如果本作品作为用户的其他作品的不可分割的一部分进行任何民事活动,本协议依旧对本作品(即该用户的其他作品的一部分)生效;若本作品完全融入该用户的其他作品之中而不可独立存在,则该用户需要保证其作品存在与本协议冲突的条款;除非该作品已获得此作品颁发者的特殊许可、或该用户即为此作品颁发者本人。
|
||||
|
||||
## 四、使用条件
|
||||
|
||||
在对此作品进行本协议所授权的民事活动中,应当同时满足以下条款:
|
||||
|
||||
1. 用户必须为此作品的任何其他接收者提供本协议的副本,在不得已无法提供副本的情况下,也应明确指示其他接收者可查阅本协议的位置。
|
||||
|
||||
2. 用户必须在修改后的作品中附带明显的通知,声明用户已更改文件,并注明更改位置。
|
||||
|
||||
3. 若用户二次分发此作品,可以选择向此作品的接收者提供无偿或有偿的担保维修、支持服务或其他责任、义务。但是,该用户只可以其自己的名义提供上述内容,不得以任何其他贡献者的名义。且该用户必须明确表明任何此类责任或义务是由其个人独立提供,且其同意并应当承担赔偿此作品的全体贡献者因其个人承担上述责任义务而产生的任何赔偿责任。
|
||||
|
||||
4. 用户不得删除或更改此作品中包含的任何许可声明(包括版权声明,专利声明,免责声明,或赔偿责任限制),除非该更改是对已知事实错误的修补、或其已获得此作品颁发者的特殊许可、或更改者即为此作品颁发者本人。
|
||||
|
||||
5. 若此作品将权益的声明通知作为一部分,那么由用户分发的任何版本的作品中须至少在下列三处之一包含该声明通知的自然人可读副本:
|
||||
|
||||
- 该作品的权益声明通知中
|
||||
- 在源形式的文件中(当且仅当该作品开放源代码)
|
||||
- 在惯例中作为第三方通知出现之处(当且仅当该作品会产生画面,且该画面可被自然人详细观察)
|
||||
|
||||
该通知的内容仅供信息提供,不应对许可证进行任何文字上的修改。用户可在其分发的作品中,在不构成修改本协议的前提下,在作品自身的声明通知或属性描述后或作为附录添加。
|
||||
|
||||
6. 依据本款第3条,若用户二次分发此作品时,选择向作品的接收者提供收费的担保服务,则必须明确告知该接收者本协议全部内容与此作品原出处,并确保其知悉上述内容;但若用户在二次分发此作品时,不选择提供任何服务,则该用户不允许向作品的接收者收取任何费用,除非该用户获得了此作品颁发者的特殊许可、或该用户即为此作品颁发者本人。
|
||||
|
||||
## 五、提交贡献
|
||||
|
||||
除非贡献者明确声明,在本作品中由该贡献者向颁发者的提供的提交,必须符合本协议的条款,并与本协议的条款不存在冲突;除非此贡献中与本协议冲突的附加条款已获得颁发者的特殊许可、或贡献者即为此作品颁发者本人。
|
||||
|
||||
## 六、商标相关
|
||||
|
||||
本协议并未授予用户,将颁发者的商标、专属标记或特定产品名称,用于合理的或惯例性的描述或此类声明之外其他任何位置的权利。
|
||||
|
||||
## 七、免责声明
|
||||
|
||||
1. 若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,**不予提供任何形式的担保、任何明示、任何暗示或类似承诺**,此类包括但不限于担保此作品毫无缺陷、担保此作品适于贩卖、担保此作品适于特定目的、担保使用此作品绝不侵权。用户将自行承担因此作品的质量或性能问题而产生的全部风险。若此作品在任何方面欠妥,将由用户(而非任何贡献者、而非任何颁发者)承担所有必要的服务、维修或除错的任何成本。本免责声明是本许可的重要组成部分。当且仅当遵守本免责声明时,本协议的其他条款中对本作品的使用授权方可生效。
|
||||
|
||||
2. 无论是因何种原因,如果不是在法律规定的特殊情况(如,确为贡献者的故意或重大过失)下或者经过了特殊准许,即使贡献者事先已知发生损害的可能,在使用本作品时,用户产生的任何直接、间接、特殊、偶然或必然造成的损失(包括但不限于商誉损失、工作延误、计算机系统故障等),**均不由任一贡献者承担**。
|
||||
|
||||
**以上是本许可协议的全部条款**
|
||||
|
||||
---
|
||||
|
||||
附录
|
||||
|
||||
**如何在自己的作品中应用 汉钰律许可协议**
|
||||
|
||||
若要在自己源形式的作品应用本协议,请在其中附加下面的通知模板,并将六角括号“〔〕”中的字段替换成自身的实际信息来替换(不包括括号本身)。这些文本必须以对应文件格式适当的注释句法包含在其中,可以是实体的纸质文档、也可以是网络公告或者计算机文件;或者脱离该源之外,另起一个新的文件,使之指向要应用本协议的那个作品。同时也建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的页面上作为版权通知的整体,这样更加容易的区分出第三方内容。
|
||||
|
||||
若需要在自己以目标形式存在的作品中应用本协议,同样需要附加下面的通知模板并更改六角括号中的字样。但是,这些文本可以是位于作品的标签上、位于作品的用户可见且能被自然人详细观察的画面之中、或者按照惯例中许可协议应该出现的位置;同时,这些文本的所处位置应当能够明确指示到本协议应用的那个作品。另外,建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的位置上作为版权通知的整体,这样更加容易的区分出第三方内容。
|
||||
|
||||
**通知模板**
|
||||
|
||||
```
|
||||
版权所有 © 〔年份〕 〔著作权人〕
|
||||
〔或者:版权所有 (C) 〔年份〕 〔著作权人〕〕
|
||||
|
||||
〔该作品〕根据 汉钰律许可协议,第一版(“本协议”)授权。
|
||||
任何人皆可从以下地址获得本协议副本:〔本协议副本所在地址〕。
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||
详细的准许和限制条款请见原协议文本。
|
||||
```
|
||||
@@ -1,71 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
音·创
|
||||
是一款免费开源的《我的世界》数字音频支持库。
|
||||
|
||||
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
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__version__ = "3.0.0-alpha"
|
||||
|
||||
__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",
|
||||
]
|
||||
@@ -1,632 +0,0 @@
|
||||
# -*- 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,
|
||||
)
|
||||
)
|
||||
|
||||
# 怎么?
|
||||
# 插件的彼此依赖就不需要什么调用了吧
|
||||
@@ -1,59 +0,0 @@
|
||||
# -*- 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
@@ -1,786 +0,0 @@
|
||||
# -*- 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,266 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储 音·创 v3 用到的一些报错类型
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||
Copyright © 2026 Eilles & YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# 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 MusicreaterBaseException(Exception):
|
||||
"""音·创 v3 的所有错误均继承于此"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""音·创 的所有错误均继承于此"""
|
||||
super().__init__("[音·创] - ", *args)
|
||||
|
||||
def meow(self):
|
||||
for i in self.args:
|
||||
print(i + "喵~", end=":")
|
||||
|
||||
def crash_it(self):
|
||||
raise self
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "".join(self.args)
|
||||
|
||||
|
||||
# =====================================
|
||||
# NOTE
|
||||
# 面对用户时候爆出去的我们认为这就是“外部错误”
|
||||
# 如果是在程序内部数据传输等情况下出现的就是“内部错误”
|
||||
# 例如,无法读取文件,这就是一个外部错误
|
||||
# 某个参数的数据类型错误,这就是内部错误
|
||||
# =====================================
|
||||
|
||||
|
||||
class MusicreaterInnerlyError(MusicreaterBaseException):
|
||||
"""内部错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""内部错误(面向开发者的报错信息)"""
|
||||
super().__init__("内部错误 - ", *args)
|
||||
|
||||
|
||||
class MusicreaterOuterlyError(MusicreaterBaseException):
|
||||
"""外部错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""外部错误(面向用户的报错信息)"""
|
||||
super().__init__("外部错误 - ", *args)
|
||||
|
||||
|
||||
class InnerlyParameterError(MusicreaterInnerlyError):
|
||||
"""内部传参错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""参数错误"""
|
||||
super().__init__("传参错误 - ", *args)
|
||||
|
||||
|
||||
class ParameterTypeError(InnerlyParameterError, TypeError):
|
||||
"""参数类型错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""参数类型错误"""
|
||||
super().__init__("参数类型错误:", *args)
|
||||
|
||||
|
||||
class ParameterValueError(InnerlyParameterError, ValueError):
|
||||
"""参数值存在错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""参数其值存在错误"""
|
||||
super().__init__("参数数值错误:", *args)
|
||||
|
||||
|
||||
class PluginNotSpecifiedError(InnerlyParameterError, LookupError):
|
||||
"""未指定插件"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""未指定插件"""
|
||||
super().__init__("未指定插件:", *args)
|
||||
|
||||
|
||||
class OuterlyParameterError(MusicreaterOuterlyError):
|
||||
"""外部参数错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""参数错误"""
|
||||
super().__init__("参数错误 - ", *args)
|
||||
|
||||
|
||||
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
|
||||
"""以0作为播放速度的错误"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""以0作为播放速度的错误"""
|
||||
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,285 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
"""
|
||||
音·创
|
||||
是一款免费开源的《我的世界》数字音频支持库。
|
||||
|
||||
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
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
|
||||
|
||||
|
||||
# BUG退散!BUG退散!
|
||||
# 异常与错误作乱之时
|
||||
# 二六字组!万国码合!二六字组!万国码合!
|
||||
# 赶快呼叫 程序员!Let's Go!
|
||||
|
||||
# BUG退散!BUG退散!
|
||||
# 異常、誤りが、困った時は
|
||||
# パラメータ メソッド!パラメータ メソッド!
|
||||
# 助けてもらおう、開発者!レッツゴー!
|
||||
|
||||
# Bug retreat! Bug retreat!
|
||||
# Exceptions and errors are causing chaos
|
||||
# Words combine! Codes unite!
|
||||
# Hurry to call the programmer! Let's Go!
|
||||
|
||||
import re
|
||||
|
||||
|
||||
from difflib import get_close_matches
|
||||
from typing import Dict, Generator, List, Optional, Tuple, Union, Mapping, Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
from .data import SingleMusic, SingleTrack
|
||||
from .exceptions import (
|
||||
FileFormatNotSupportedError,
|
||||
PluginNotSpecifiedError,
|
||||
PluginNotFoundError,
|
||||
)
|
||||
from ._plugin_abc import TopPluginBase
|
||||
from .plugins import (
|
||||
_global_plugin_registry,
|
||||
PluginRegistry,
|
||||
PluginConfig,
|
||||
PluginTypes,
|
||||
load_plugin_module,
|
||||
T_IOPlugin,
|
||||
T_Plugin,
|
||||
)
|
||||
|
||||
|
||||
class MusiCreater:
|
||||
"""
|
||||
音·创 v3 主要控制类
|
||||
另:“创建者”一词的英文应该是“Creator”
|
||||
"""
|
||||
|
||||
__plugin_registry: PluginRegistry
|
||||
"""插件注册表实例"""
|
||||
_plugin_cache: Dict[str, TopPluginBase]
|
||||
"""插件缓存字典,插件id为键、插件实例为值"""
|
||||
music: SingleMusic
|
||||
"""当前曲目实例"""
|
||||
|
||||
def __init__(self, whole_music: SingleMusic) -> None:
|
||||
global _global_plugin_registry
|
||||
|
||||
self.__plugin_registry = _global_plugin_registry
|
||||
|
||||
self._plugin_cache = {}
|
||||
self._cache_all_plugins()
|
||||
|
||||
self.music = whole_music
|
||||
|
||||
@staticmethod
|
||||
def _get_plugin_within_iousage(
|
||||
get_func: Callable[[Union[Path, str]], Generator[T_IOPlugin, None, None]],
|
||||
fpath: Path,
|
||||
plg_regdict: Dict[str, T_IOPlugin],
|
||||
plg_id: Optional[str],
|
||||
) -> T_IOPlugin:
|
||||
|
||||
__plugin: Optional[T_IOPlugin] = None
|
||||
if plg_id:
|
||||
__plugin = plg_regdict.get(plg_id)
|
||||
|
||||
else:
|
||||
for __plg in get_func(fpath):
|
||||
if __plugin:
|
||||
raise PluginNotSpecifiedError(
|
||||
"文件类型`{}`可被多个插件处理,请在调用函数的参数中指定插件名称".format(
|
||||
fpath.suffix.upper()
|
||||
)
|
||||
)
|
||||
__plugin = __plg
|
||||
if __plugin:
|
||||
return __plugin
|
||||
else:
|
||||
raise FileFormatNotSupportedError(
|
||||
"无法找到处理`{}`类型文件的插件".format(fpath.suffix.upper())
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def import_music(
|
||||
cls,
|
||||
file_path: Path,
|
||||
plugin_id: Optional[str] = None,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
) -> "MusiCreater":
|
||||
return cls(
|
||||
whole_music=cls._get_plugin_within_iousage(
|
||||
_global_plugin_registry.get_music_input_plugin_by_format,
|
||||
file_path,
|
||||
_global_plugin_registry._music_input_plugins,
|
||||
plugin_id,
|
||||
).load(file_path, plugin_config)
|
||||
)
|
||||
|
||||
def import_track(
|
||||
self,
|
||||
file_path: Path,
|
||||
plugin_id: Optional[str] = None,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
) -> SingleTrack:
|
||||
self.music.append(
|
||||
self._get_plugin_within_iousage(
|
||||
self.__plugin_registry.get_track_input_plugin_by_format,
|
||||
file_path,
|
||||
self.__plugin_registry._track_input_plugins,
|
||||
plugin_id,
|
||||
).load(file_path, plugin_config)
|
||||
)
|
||||
return self.music[-1]
|
||||
|
||||
def export_music(
|
||||
self,
|
||||
file_path: Path,
|
||||
plugin_id: Optional[str] = None,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
) -> None:
|
||||
self._get_plugin_within_iousage(
|
||||
self.__plugin_registry.get_music_output_plugin_by_format,
|
||||
file_path,
|
||||
self.__plugin_registry._music_output_plugins,
|
||||
plugin_id,
|
||||
).dump(self.music, file_path, plugin_config)
|
||||
|
||||
def export_track(
|
||||
self,
|
||||
track_index: int,
|
||||
file_path: Path,
|
||||
plugin_id: Optional[str] = None,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
) -> None:
|
||||
self._get_plugin_within_iousage(
|
||||
self.__plugin_registry.get_track_output_plugin_by_format,
|
||||
file_path,
|
||||
self.__plugin_registry._track_output_plugins,
|
||||
plugin_id,
|
||||
).dump(self.music[track_index], file_path, plugin_config)
|
||||
|
||||
def perform_operation_on_music(
|
||||
self, plugin_id: str, plugin_config: Optional[PluginConfig] = None
|
||||
):
|
||||
if __plugin := self.__plugin_registry._music_operate_plugins.get(plugin_id):
|
||||
# 这样做是为了兼容以后的*撤回/重做*功能
|
||||
self.music = __plugin.process(self.music, plugin_config)
|
||||
else:
|
||||
raise PluginNotFoundError(
|
||||
"无法找到惟一识别码为`{}`的插件".format(plugin_id)
|
||||
)
|
||||
|
||||
def perform_operation_on_track(
|
||||
self,
|
||||
track_index: int,
|
||||
plugin_id: str,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
):
|
||||
if __plugin := self.__plugin_registry._track_operate_plugins.get(plugin_id):
|
||||
# 这样做是为了兼容以后的*撤回/重做*功能
|
||||
self.music[track_index] = __plugin.process(
|
||||
self.music[track_index], plugin_config
|
||||
)
|
||||
else:
|
||||
raise PluginNotFoundError(
|
||||
"无法找到惟一识别码为`{}`的插件".format(plugin_id)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _camel_to_snake(name: str) -> str:
|
||||
"""
|
||||
将驼峰命名转换为蛇形命名
|
||||
CyberAngel -> cyber_angel
|
||||
"""
|
||||
return re.sub(
|
||||
"([a-z0-9])([A-Z])", r"\1_\2", re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
||||
).lower()
|
||||
|
||||
def _parse_plugin_id(self, attr_name: str) -> Optional[str]:
|
||||
"""解析属性名称为插件惟一识别码"""
|
||||
|
||||
# 尝试去除 _plugin 后缀
|
||||
if attr_name.endswith("_plugin"):
|
||||
candidate_name = attr_name[:-7] # 去除 "_plugin"
|
||||
if candidate_name in self._plugin_cache:
|
||||
return candidate_name
|
||||
|
||||
# 尝试转换为 snake_case(如果插件名是驼峰式)
|
||||
snake_case_name = self._camel_to_snake(attr_name)
|
||||
|
||||
if snake_case_name != attr_name: # 避免重复转换
|
||||
if snake_case_name in self._plugin_cache: # 尝试转换后的插件名
|
||||
return snake_case_name
|
||||
else:
|
||||
return self._parse_plugin_id(snake_case_name)
|
||||
|
||||
return None
|
||||
|
||||
def _get_closest_plugin_id(self, requested_id: str) -> Optional[str]:
|
||||
"""找到最接近的插件识别码(用于更好的错误提示)"""
|
||||
|
||||
matches = get_close_matches(
|
||||
requested_id, self._plugin_cache.keys(), n=1, cutoff=0.6
|
||||
)
|
||||
return matches[0] if matches else None
|
||||
|
||||
def get_plugin_by_id(self, plg_id: str):
|
||||
"""获取插件实例,并缓存起来,提高性能"""
|
||||
if plg_id.startswith("_"):
|
||||
raise AttributeError("属性`{}`不存在,不应访问类的私有属性".format(plg_id))
|
||||
|
||||
if plg_id in self._plugin_cache:
|
||||
return self._plugin_cache[plg_id]
|
||||
else:
|
||||
plugin_name = self._parse_plugin_id(plg_id)
|
||||
if plugin_name:
|
||||
self._plugin_cache[plg_id] = self._plugin_cache[plugin_name]
|
||||
return self._plugin_cache[plg_id]
|
||||
|
||||
closest = self._get_closest_plugin_id(plg_id)
|
||||
|
||||
raise AttributeError(
|
||||
"插件`{}`不存在,请检查插件的惟一识别码是否正确".format(plg_id)
|
||||
+ (
|
||||
";或者阁下可能想要使用的是`{}`插件?".format(closest)
|
||||
if closest
|
||||
else ""
|
||||
)
|
||||
)
|
||||
|
||||
def __getattr__(self, plugin_id: str):
|
||||
"""动态属性访问,允许直接 实例.插件名 来访问插件"""
|
||||
return self.get_plugin_by_id(plugin_id)
|
||||
|
||||
def _cache_all_plugins(self):
|
||||
"""获取所有已注册插件的名称"""
|
||||
for __plugin_type, __plugins_map in self.__plugin_registry:
|
||||
for __plugin_id, __plugin in __plugins_map.items():
|
||||
if __plugin_id in self._plugin_cache: # 避免重复缓存
|
||||
if (
|
||||
__plugin.metainfo.version
|
||||
<= self._plugin_cache[__plugin_id].metainfo.version
|
||||
): # 优先使用版本号最大的插件
|
||||
continue
|
||||
self._plugin_cache[__plugin_id] = __plugin
|
||||
@@ -1,583 +0,0 @@
|
||||
# -*- 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,424 +0,0 @@
|
||||
# -*- 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,25 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储 音·创 v3 定义的一些数据类型,可以用于类型检查器
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||
Copyright © 2026 Eilles & YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 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
|
||||
|
||||
FittingFunctionType = Callable[[float], float]
|
||||
"""
|
||||
拟合函数类型
|
||||
"""
|
||||
145
README.md
145
README.md
@@ -1,136 +1,49 @@
|
||||
[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
|
||||
# 音·创 Musicreater
|
||||
|
||||
<h1 align="center">音·创 Musicreater </h1>
|
||||
### 介绍
|
||||
音·创(Musicreater)是由金羿(W-YI)开发的一款《我的世界》基岩版音乐生成辅助软件
|
||||
|
||||
<p align="center">
|
||||
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png">
|
||||
</img>
|
||||
</p>
|
||||
欢迎加群:861684859
|
||||
|
||||
<h3 align="center">一款免费开源的《我的世界》数字音频支持库。</h3>
|
||||
### 软件架构
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
||||
<a href='https://gitee.com/TriM-Organization/Musicreater'>
|
||||
<img align="right" src='https://gitee.com/TriM-Organization/Musicreater/widgets/widget_1.svg' alt='Fork me on Gitee'>
|
||||
</img>
|
||||
</a>
|
||||
<p>
|
||||
软件采用Python作为第一语言,目前还没有使用其他语言辅助。使用BeeWare作为图形库兼容安卓。
|
||||
|
||||
[![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
|
||||
[![][Bilibili: 玉衡Alioth]](https://space.bilibili.com/604072474)
|
||||
[![CodeStyle: black]](https://github.com/psf/black)
|
||||
[![][python]](https://www.python.org/)
|
||||
[![][license]](LICENSE)
|
||||
[![][release]](../../releases)
|
||||
|
||||
[](https://gitee.com/TriM-Organization/Musicreater/stargazers)
|
||||
[](https://gitee.com/TriM-Organization/Musicreater/members)
|
||||
[](https://github.com/TriM-Organization/Musicreater/stargazers)
|
||||
[](https://github.com/TriM-Organization/Musicreater/forks)
|
||||
|
||||
简体中文🇨🇳 | [English🇬🇧](README_EN.md)
|
||||
|
||||
## 介绍 🚀
|
||||
|
||||
音·创 是一款免费开源的针对 **《我的世界》** 音乐的支持库
|
||||
|
||||
欢迎加群:[861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
||||
|
||||
> **注意** 本仓库内的项目仅仅是支持库,其用户为基岩版音乐相关软件的开发者
|
||||
>
|
||||
> 面向常规用户的 **基岩版音乐转换工具** 请参阅:[伶伦转换器](../../../Linglun-Converter)
|
||||
>
|
||||
> 我们也正在开发面向高级用户的 **基岩版音乐编辑工具**(数字音频工作站):[伶伦](../../../LinglunStudio)
|
||||
尽量全平台支持
|
||||
|
||||
|
||||
## 安装 🔳
|
||||
### 安装教程
|
||||
|
||||
- 使用 pypi
|
||||
#### Windows
|
||||
|
||||
```bash
|
||||
pip install --upgrade Musicreater
|
||||
```
|
||||
即将到来。
|
||||
|
||||
- 如果无法更新最新,可以尝试:
|
||||
#### Linux
|
||||
|
||||
```bash
|
||||
pip install --upgrade -i https://pypi.python.org/simple Musicreater
|
||||
```
|
||||
即将到来。
|
||||
|
||||
- 克隆仓库并安装(最新内容但**不推荐**)
|
||||
#### Android
|
||||
|
||||
```bash
|
||||
git clone https://gitee.com/TriM-Organization/Musicreater.git
|
||||
cd Musicreater
|
||||
python setup.py install
|
||||
```
|
||||
即将到来。
|
||||
|
||||
以上命令中 `python`、`pip` 请依照各个环境不同灵活更换,可能为`python3`或`pip3`之类。
|
||||
### 使用说明
|
||||
|
||||
## 文档 📄
|
||||
1. 直接运行就好
|
||||
2. 有不懂的问题来群里问
|
||||
3. 请理解英文表述
|
||||
|
||||
[生成文件的使用](./docs/%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md)
|
||||
### 致谢
|
||||
|
||||
[仓库 API 文档](./docs/%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)
|
||||
1. 感谢由 [Fuckcraft](https://github.com/fuckcraft) “鸣凤鸽子”等 带来的我的世界websocket服务器功能
|
||||
2. 感谢 昀梦<QQ1515399885> 找出指令生成错误bug并指正
|
||||
3. 感谢由 Charlie_Ping “查理平” 带来的bdx转换功能
|
||||
4. 感谢由 CMA_2401PT 提供的 BDXWorkShop作为.bdx结构的操作指导
|
||||
5. 感谢广大群友为此程序提供的测试等支持
|
||||
6. 若您为我找出了错误但您的名字没有显示在此列表中,请联系我!
|
||||
|
||||
## 作者 ✒
|
||||
|
||||
**金羿 Eilles**:我的世界基岩版指令作者,个人开发者,B 站不知名 UP 主。
|
||||
### 作者<金羿>联系方式
|
||||
|
||||
**玉衡Alioth Alioth**:我的世界基岩版玩家,喜欢编程和音乐,学生。
|
||||
|
||||
**偷吃不是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 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
|
||||
- 感谢 **[神羽 “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) 项目的开发为我们提供持续的追赶动力。
|
||||
|
||||
> 感谢广大群友为此库提供的测试和建议等
|
||||
> 若您对我们有所贡献但您的名字没有出现在此列表中,请联系我们!
|
||||
|
||||
## 联系 📞
|
||||
|
||||
若遇到库中的问题,欢迎在[此](https://gitee.com/TriM-Organization/Musicreater/issues/new)提出你的 issue。
|
||||
|
||||
如果需要与开发组进行交流,欢迎加入我们的[开发闲聊 Q 群](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)。
|
||||
|
||||
亦可以联系我们[睿乐组织官方邮箱](mailto:TriM-Organization@hotmail.com),取得进一步联系!
|
||||
|
||||
---
|
||||
|
||||
此项目并非一个官方 《我的世界》(_Minecraft_)项目
|
||||
|
||||
此项目不隶属或关联于 Mojang Studios 或 微软
|
||||
|
||||
此项目亦不隶属或关联于 网易
|
||||
|
||||
“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.
|
||||
1. QQ 2647547478
|
||||
2. 电邮 EillesWan2006@163.com W-YI_DoctorYI@outlook.com
|
||||
3. 微信 WYI_DoctorYI
|
||||
12
README.rst
Normal file
12
README.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
Musicreater
|
||||
===========
|
||||
|
||||
**This cross-platform app was generated by** `Briefcase`_ **- part of**
|
||||
`The BeeWare Project`_. **If you want to see more tools like Briefcase, please
|
||||
consider** `becoming a financial member of BeeWare`_.
|
||||
|
||||
音·创(Musicreater)是由金羿(W-YI)开发的一款《我的世界》基岩版音乐生成辅助软件
|
||||
|
||||
.. _`Briefcase`: https://github.com/beeware/briefcase
|
||||
.. _`The BeeWare Project`: https://beeware.org/
|
||||
.. _`becoming a financial member of BeeWare`: https://beeware.org/contributing/membership
|
||||
138
README_EN.md
138
README_EN.md
@@ -1,138 +0,0 @@
|
||||
[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>
|
||||
|
||||
<p align="center">
|
||||
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
|
||||
</img>
|
||||
</p>
|
||||
|
||||
<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">
|
||||
</img>
|
||||
<p>
|
||||
|
||||
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
|
||||
[![][Bilibili: Alioth]](https://space.bilibili.com/604072474)
|
||||
[![CodeStyle: black]](https://github.com/psf/black)
|
||||
[![][python]](https://www.python.org/)
|
||||
[![][license]](LICENSE)
|
||||
[![][release]](../../releases)
|
||||
|
||||
[](https://gitee.com/TriM-Organization/Musicreater/stargazers)
|
||||
[](https://gitee.com/TriM-Organization/Musicreater/members)
|
||||
[](https://github.com/TriM-Organization/Musicreater/stargazers)
|
||||
[](https://github.com/TriM-Organization/Musicreater/forks)
|
||||
|
||||
[简体中文 🇨🇳](README.md) | English🇬🇧
|
||||
|
||||
**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 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
|
||||
|
||||
```bash
|
||||
pip install Musicreater --upgrade
|
||||
```
|
||||
|
||||
- If above command cannot fetch latest version, try:
|
||||
|
||||
```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)
|
||||
|
||||
[生成文件的使用](./docs/%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md)
|
||||
|
||||
[仓库 API 文档](./docs/%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)
|
||||
|
||||
### Authors ✒
|
||||
|
||||
**Eilles (金羿)**:A student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
|
||||
|
||||
**Alioth (玉衡Alioth)**: A student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
|
||||
|
||||
**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 <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](../../issues/new)!
|
||||
|
||||
Want to get in contact of developers? Welcome to join our [Chat QQ group](https://jq.qq.com/?_wv=1027&k=hpeRxrYr).
|
||||
|
||||
Or contact us via [TriM-Org Official Email](mailto:TriM-Organization@hotmail.com)!
|
||||
|
||||
---
|
||||
|
||||
NOT AN OFFICIAL MINECRAFT PRODUCT.
|
||||
|
||||
NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
|
||||
|
||||
NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
|
||||
|
||||
此项目并非一个官方 《我的世界》(_Minecraft_)项目
|
||||
|
||||
此项目不隶属或关联于 Mojang Studios 或 微软
|
||||
|
||||
此项目亦不隶属或关联于 网易 相关
|
||||
|
||||
“Minecraft”是 Mojang Synergies AB 的商标,此项目中所有对于“我的世界”、“Minecraft”等相关称呼均为必要的介绍性使用
|
||||
|
||||
- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易璀璨网络科技有限公司
|
||||
52
README_en.md
Normal file
52
README_en.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Musicreater
|
||||
|
||||
### Introduction
|
||||
Musicreater(音·创) is an Eilles(*W-YI*)'s app that is used for creating musics in **Minecraft: Bedrock Edition**.
|
||||
|
||||
Welcome to join our QQ group: 861684859
|
||||
|
||||
### Framework
|
||||
|
||||
Use *Python* to develop, use *BeeWare* as a Windows Library.
|
||||
|
||||
We are trying to support every platform.
|
||||
|
||||
|
||||
### Tutorials
|
||||
|
||||
#### Windows
|
||||
|
||||
Please wait for a while...
|
||||
Comming soon
|
||||
|
||||
#### Linux
|
||||
|
||||
Please wait for a while...
|
||||
Comming soon
|
||||
|
||||
#### Android
|
||||
|
||||
Please wait for a while...
|
||||
Comming soon
|
||||
|
||||
### Instructions
|
||||
|
||||
1. Just make u understand the Chinese
|
||||
2. If u dont understand, u can come to the QQ group or email me to ask questions
|
||||
3. The English Edition is comming soon.
|
||||
|
||||
### Thanks
|
||||
|
||||
1. Thank [Fuckcraft](https://github.com/fuckcraft) “鸣凤鸽子”and so on for the function of Creating the Websocket Server for Minecraft: Bedrock Edition.
|
||||
2. Thank 昀梦<QQ1515399885> for finding and correcting the bugs in the commands that *Musicreater* Created.
|
||||
3. Thank Charlie_Ping “查理平” for bdx convert funtion.
|
||||
4. Thank CMA_2401PT for BDXWorkShop as the .bdx structure's operation guide.
|
||||
5. Thanks for a lot of groupmates who support me and help me to test the program.
|
||||
6. If u have give me some help but u haven't been in the list, please contact me.
|
||||
|
||||
|
||||
### Contact *Eilles(W-YI)*(金羿)
|
||||
|
||||
1. QQ 2647547478
|
||||
2. E-mail EillesWan2006@163.com W-YI_DoctorYI@outlook.com EillesWan@outlook.com
|
||||
3. WeChat WYI_DoctorYI
|
||||
1
Run in devmode.bat
Normal file
1
Run in devmode.bat
Normal file
@@ -0,0 +1 @@
|
||||
briefcase dev
|
||||
60
Run the Ver Build under Tkinter.py
Normal file
60
Run the Ver Build under Tkinter.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os,shutil
|
||||
from sys import platform
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
print("更新执行位置...")
|
||||
if platform == 'win32':
|
||||
try:
|
||||
os.chdir(__file__[:len(__file__)-__file__[len(__file__)::-1].index('\\')]+'src\\')
|
||||
print("更新执行位置,当前文件位置"+__file__)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
os.chdir(__file__[:len(__file__)-__file__[len(__file__)::-1].index('/')]+'src/')
|
||||
except:
|
||||
pass
|
||||
print("其他平台:"+platform+"更新执行位置,当前文件位置"+__file__)
|
||||
print('完成!')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
try:
|
||||
import toga,amulet
|
||||
except:
|
||||
print("You'd better install the libraries of this app\nNow, we're helping you with this.")
|
||||
from src.musicreater.msctspt.bugReporter import version
|
||||
version.installLibraries(version)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if platform == 'win32':
|
||||
os.system("python ./Musicreater.py")
|
||||
elif platform == 'linux':
|
||||
os.system("python3 ./Musicreater.py")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
try:
|
||||
if os.path.exists("./log/"):
|
||||
shutil.rmtree("./log/")
|
||||
if os.path.exists("./logs/"):
|
||||
shutil.rmtree("./logs/")
|
||||
if os.path.exists("./cache/"):
|
||||
shutil.rmtree("./cache/")
|
||||
except:
|
||||
print("无法清除日志及临时文件")
|
||||
48
TO-DO.md
48
TO-DO.md
@@ -1,48 +0,0 @@
|
||||
# 任务清单
|
||||
|
||||
## 待办事项
|
||||
- 乐曲文件格式设计
|
||||
目前想到的是:
|
||||
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. 插件依赖性的优化。目前没有处理各个插件依赖关系的问题,如果插件之间彼此依赖要怎么做?
|
||||
我的想法是,这个依赖的处理由调用端来完成。比如我们的 伶伦工作站 是以 音·创 为核心的一个可视化数字音频工作站。
|
||||
那么应该由伶伦来处理依赖关系并加载之。
|
||||
@@ -1,22 +0,0 @@
|
||||
import shutil
|
||||
import os
|
||||
from rich.console import Console
|
||||
from rich.progress import track
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
def main():
|
||||
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="正删档"):
|
||||
if os.path.isdir(file) and os.access(file, os.W_OK):
|
||||
shutil.rmtree(file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
13
docs/API.md
13
docs/API.md
@@ -1,13 +0,0 @@
|
||||
<h1 align="center">音·创 Musicreater</h1>
|
||||
|
||||
<p align="center">
|
||||
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
|
||||
</p>
|
||||
|
||||
**此为开发相关文档,内容包括:库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释**
|
||||
|
||||
# [main.py](../Musicreater/main.py)
|
||||
|
||||
## [类] MidiConvert
|
||||
|
||||
### [类函数] from_midi_file
|
||||
@@ -1,315 +0,0 @@
|
||||
<h1 align="center">音·创 Musicreater</h1>
|
||||
|
||||
<p align="center">
|
||||
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
|
||||
</p>
|
||||
|
||||
**此为开发相关文档,内容包括:库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释**
|
||||
|
||||
# 库的简单调用
|
||||
|
||||
参见[example.py的相关部分](../example.py),使用此库进行MIDI转换非常简单。
|
||||
|
||||
- 在导入转换库后,使用 MidiConvert 类建立转换对象(读取Midi文件)
|
||||
|
||||
音·创库支持新旧两种execute语法,需要在对象实例化时指定
|
||||
```python
|
||||
# 导入音·创库
|
||||
import Musicreater
|
||||
|
||||
# 指定是否使用旧的execute指令语法(即1.18及以前的《我的世界:基岩版》语法)
|
||||
old_execute_format = False
|
||||
|
||||
# 可以通过文件地址自动读取
|
||||
cvt_mid = Musicreater.MidiConvert.from_midi_file(
|
||||
"Midi文件地址",
|
||||
old_exe_format=old_execute_format
|
||||
)
|
||||
|
||||
# 也可以导入Mido对象
|
||||
cvt_mid = Musicreater.MidiConvert(
|
||||
mido.MidiFile("Midi文件地址"),
|
||||
"音乐名称",
|
||||
old_exe_format=old_execute_format
|
||||
)
|
||||
```
|
||||
|
||||
- 获取 Midi 音乐经转换后的播放指令
|
||||
|
||||
```python
|
||||
# 通过函数 to_command_list_in_score, to_command_list_in_delay
|
||||
# 分别可以得到
|
||||
# 以计分板作为播放器的指令对象列表、以延迟作为播放器的指令对象列表
|
||||
# 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
|
||||
|
||||
# 使用 to_command_list_in_score 函数进行转换之后,返回值有三个
|
||||
# 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表
|
||||
# 也就是列表套列表
|
||||
# 但是,在对象内部所存储的数据却不会如此嵌套
|
||||
command_channel_list, command_count, max_score = cvt_mid.to_command_list_in_score(
|
||||
"计分板名称",
|
||||
1.0, # 音量比率
|
||||
1.0, # 速度倍率
|
||||
)
|
||||
|
||||
# 使用 to_command_list_in_delay 转换后的返回值只有两个
|
||||
# 但是第一个返回值没有列表套列表
|
||||
command_list, max_delay = cvt_mid.to_command_list_in_delay(
|
||||
1.0, # 音量比率
|
||||
1.0, # 速度倍率
|
||||
"@a", # 玩家选择器
|
||||
)
|
||||
|
||||
# 运行之后,指令和总延迟会存储至对象内
|
||||
print(
|
||||
"音乐长度:{}/游戏刻".format(
|
||||
cvt_mid.music_tick_num
|
||||
)
|
||||
)
|
||||
print(
|
||||
"指令如下:\n{}".format(
|
||||
cvt_mid.music_command_list
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
- 除了获取播放指令外,还可以获取进度条指令
|
||||
|
||||
```python
|
||||
# 通过函数 form_progress_bar 可以获得
|
||||
# 以计分板为载体所生成的进度条的指令对象列表
|
||||
# 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
|
||||
|
||||
# 使用 form_progress_bar 函数进行转换之后,返回值有三个
|
||||
# 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表
|
||||
# 也就是列表套列表
|
||||
cvt_mid.form_progress_bar(
|
||||
max_score, # 音乐时长游戏刻
|
||||
scoreboard_name, # 进度条使用的计分板名称
|
||||
progressbar_style, # 进度条样式组(详见下方)
|
||||
)
|
||||
|
||||
# 同上面生成播放指令的理,进度条指令也会存储至对象内
|
||||
print(
|
||||
"进度条指令如下:\n{}".format(
|
||||
cvt_mid.progress_bar_command
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
在上面的代码中,进度条样式是可以自定义的,详见[下方说明](%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md#进度条自定义)。
|
||||
|
||||
- 转换成指令是一个方面,接下来是再转换为可以导入MC的格式。我们提供了 **音·创** 内置的附加组件,可以借助 `MidiConvert` 对象转换为相应格式。
|
||||
|
||||
```python
|
||||
# 导入 Musicreater
|
||||
import Musicreater
|
||||
# 导入附加组件功能
|
||||
import Musicreater.plugin
|
||||
|
||||
|
||||
# 导入相应的文件格式转换功能
|
||||
|
||||
# 转换为函数附加包
|
||||
import Musicreater.plugin.funpack
|
||||
# 转换为 BDX 结构文件
|
||||
import Musicreater.plugin.bdxfile
|
||||
# 转换为 mcstructure 结构文件
|
||||
import Musicreater.plugin.mcstructfile
|
||||
# 转换为结构附加包
|
||||
import Musicreater.plugin.mcstructpack
|
||||
# 直接通过 websocket 功能播放(正在开发)
|
||||
import Musicreater.plugin.websocket
|
||||
|
||||
|
||||
# 定义转换参数
|
||||
cvt_cfg = Musicreater.plugin.ConvertConfig(
|
||||
output_path,
|
||||
volumn, # 音量大小参数
|
||||
speed, # 速度倍率
|
||||
progressbar, # 进度条样式组(详见下方)
|
||||
)
|
||||
|
||||
# 使用附加组件转换,其调用的函数应为:
|
||||
# Musicreater.plugin.输出格式.播放器格式
|
||||
# 值得注意的是,并非所有输出格式都支持所有播放器格式
|
||||
# 调用的时候还请注意甄别
|
||||
# 例如,以下函数是将 MidiConvert 对象 cvt_mid
|
||||
# 以 cvt_cfg 指定的参数
|
||||
# 以延迟播放器转换为 mcstructure 文件
|
||||
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
cvt_mid,
|
||||
cvt_cfg,
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
# 生成文件结构
|
||||
|
||||
## 名词解释
|
||||
|
||||
|名词|解释|备注|
|
||||
|--------|-----------|----------|
|
||||
|指令区|一个用于放置指令系统的区域,通常是常加载区。|常见于服务器指令系统、好友联机房间中|
|
||||
|指令链(链)|与链式指令方块不同,一个指令链通常指代的是一串由某种非链式指令方块作为开头,后面连着一串链式指令方块的结构。|通常的链都应用于需要“单次激活而多指令”的简单功能|
|
||||
|起始块|链最初的那个非链式指令方块。|此方块为脉冲方块或重复方块皆可|
|
||||
|指令系统(系统)|指令系统通常指的是,由一个或多个指令链以及相关红石机构相互配合、一同组成的,为达到某种特定的功能而构建的整体结构。|通常的系统都应用于需要“综合调配指令”的复杂功能。可由多个实现不同功能的模块构成,不同系统之间可以相互调用各自的模块。|
|
||||
|游戏刻(刻)|游戏的一刻是指《我的世界》的游戏进程循环运行一次所占用的时间。([详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E6%B8%B8%E6%88%8F%E5%88%BB))。指令方块的延迟功能(即指令方块的“延迟刻数”设置项,此项的名称被误译为“已选中项的延迟”)的单位即为`1`游戏刻。|正常情况下,游戏固定以每秒钟 $20$ 刻的速率运行。但是,由于游戏内的绝大多数操作都是基于游戏进程循环而非现实中的时间来计时并进行的,一次游戏循环内也许会发生大量的操作,更多情况下,一秒对应的游戏刻会更少。|
|
||||
|红石刻|一个红石刻代表了两个游戏刻。([详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E7%BA%A2%E7%9F%B3%E5%88%BB))。红石中继器会带来 $1$~$4$ 个红石刻的延迟,其默认的延迟时间为 $1$ 红石刻。|正常情况下,红石信号在一个红石电路中传输回存在 $\frac{1}{10}$ 秒左右的延迟。但是,同理于游戏刻,一秒对应的红石刻是不定的。|
|
||||
|
||||
## 播放器
|
||||
|
||||
**音·创**生成的文件可以采用多种方式播放,一类播放方式,我们称其为**播放器**,例如**延迟播放器**和**计分板播放器**等等,以后推出的新的播放器,届时也会在此处更新。
|
||||
|
||||
为什么要设计这么多播放器?是为了适应不同的播放环境需要。通常情况下,一个音乐中含有多个音符,音符与音符之间存在间隔,这里就产生了不一样的,实现音符间时间间隔的方式。而不同的应用环境下,又会产生不一样的要求。接下来将对不同的播放器进行详细介绍。
|
||||
|
||||
### 参数释义
|
||||
|
||||
|参数|说明|备注|
|
||||
|--------|-----------|----------|
|
||||
|`ScBd`|指定的计分板名称||
|
||||
|`Tg`|播放对象|选择器或玩家名|
|
||||
|`x`|音发出时对应的分数值||
|
||||
|`InstID`|声音效果ID|不同的声音ID可以对应不同的乐器,详见转换[乐器对照表](./%E8%BD%AC%E6%8D%A2%E4%B9%90%E5%99%A8%E5%AF%B9%E7%85%A7%E8%A1%A8.md)|
|
||||
|`Ht`|播放点对玩家的距离|通过距离来表达声音的响度。以 $S$ 表示此参数`Ht`,以Vol表示音量百分比,则计算公式为: $S = \frac{1}{Vol}-1$ |
|
||||
|`Vlct`|原生我的世界中规定的播放力度|这个参数是一个谜一样的存在,似乎它的值毫不重要……因为无论这个值是多少,我们听起来都差不多。当此音符所在MIDI通道为第一通道,则这个值为 $0.7$ 倍MIDI指定力度,其他则为 $0.9$ 倍。|
|
||||
|`Ptc`|音符的音高|这是决定音调的参数。以 $P$ 表示此参数, $n$ 表示其在MIDI中的编号, $x$ 表示一定的音调偏移,则计算公式为: $P = 2^\frac{n-60-x}{12}$。之所以存在音调偏移是因为在《我的世界》中,不同的[乐器存在不同的音域](https://zh.minecraft.wiki/wiki/%E9%9F%B3%E7%AC%A6%E7%9B%92#%E4%B9%90%E5%99%A8),我们通过音调偏移来进行调整。|
|
||||
|
||||
### 播放器内容
|
||||
|
||||
1. 计分板播放器
|
||||
|
||||
计分板播放器是一种传统的《我的世界》音乐播放方式。通过对于计分板加分来实现播放不同的音符。一个很简单的原理,就是**用不同的计分板分值对应不同的音符**,再通过加分,来达到那个分值,即播放出来。
|
||||
|
||||
在**音·创**中,用来达到这种效果的指令是这样的:
|
||||
|
||||
```mcfunction
|
||||
execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
|
||||
```
|
||||
|
||||
|
||||
|
||||
后四个参数决定了这个音的性质,而前两个参数仅仅是为了决定音播放的时间。
|
||||
|
||||
2. 延迟播放器
|
||||
|
||||
延迟播放器是通过《我的世界》游戏中,指令方块的设置项“延迟刻数”来达到定位音符的效果。**将所有的音符依照其播放时距离乐曲开始时的时间(毫秒),放在一个序列内,再计算音符两两之间对应的时间差值,转换为《我的世界》内对应的游戏刻数之后填入指令方块的设置中。**
|
||||
|
||||
在**音·创**中,由于此方式播放的音乐不需要用计分板,所以播放指令是这样的:
|
||||
|
||||
```mcfunction
|
||||
execute Tg ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
|
||||
```
|
||||
|
||||
|
||||
其中后四个参数决定了这个音的性质。
|
||||
|
||||
由于这样的延迟数据是依赖于指令方块的设置项,所以使用这种播放器所转换出的结果仅可以存储在包含方块NBT信息及方块实体NBT信息的结构文件中,或者直接输出至世界。
|
||||
|
||||
3. 中继器播放器
|
||||
|
||||
中继器播放器是一种传统的《我的世界》红石音乐播放方式,利用游戏内“红石组件”之“红石中继器”以达到定位音符之用。**但是,中继器的延迟为1红石刻**
|
||||
|
||||
|
||||
## 文件格式
|
||||
|
||||
1. 附加包格式(`.mcpack`)
|
||||
|
||||
使用附加包格式导出音乐,若采用计分板 播放器,则音乐会以指令函数文件(`.mcfunction`)存储于附加包内。而若为延迟或中继器播放器,则音乐回以结构文件(`.mcstructure`)存储。在所生成的附加包中,函数文件的存储结构应为:
|
||||
|
||||
- `functions\`
|
||||
- `index.mcfunction`
|
||||
- `stop.mcfunction`
|
||||
- `mscply\`
|
||||
- `progressShow.mcfunction`
|
||||
- `track1.mcfunction`
|
||||
- `track2.mcfunction`
|
||||
- ...
|
||||
- `trackN.mcfunction`
|
||||
- `structures\`
|
||||
- `XXX_main.mcstructure`
|
||||
- `XXX_start.mcstructure`
|
||||
- `XXX_reset.mcstructure`
|
||||
- `XXX_pgb.mcstructure`
|
||||
|
||||
如图,其中,`index.mcfunction`文件、`stop.mcfunction`文件和`mscply`文件夹存在于函数目录的根下;在`mscply`目录中,包含音乐导出的众多音轨播放文件(`trackX.mcfunction`)。同时,若使用计分板播放器生成此包时启用生成进度条,则会包含`progressShow.mcfunction`文件。若选择延迟或中继器播放器,则会生成`structures`目录以及相关`.mcstructure`文件,其中`mian`表示音乐播放用的主要结构;`start`是用于初始化播放的部分,仅包含一个指令方块即起始块;`reset`和`pgb`仅在启用生成进度条时出现,前者用于重置临时计分板,后者用于显示进度条。
|
||||
|
||||
`index.mcfunction`用于开始播放:
|
||||
|
||||
1. 若为计分板播放器,则其中包含打开各个音轨对应函数的指令,以及加分指令,这里的加分,是将**播放计分板的值大于等于 $1$ 的所有玩家**的播放计分板分数增加 $1$。同时,若生成此包时选择了自动重置计分板的选项,则会包含一条重置计分板的指令。
|
||||
|
||||
2. 若为延迟或中继器播放器,则其中的指令仅包含用以正确加载结构的`structure`指令。
|
||||
|
||||
`stop.mcfunction`用于终止播放:
|
||||
|
||||
1. 若为计分板播放器,则其中包含将**全体玩家的播放计分板**重置的指令。
|
||||
|
||||
2. 若为延迟或中继器播放器,则其中包含**停用命令方块**和**启用命令方块**的指令。~~然鹅实际上对于播放而言是一点用也没有~~
|
||||
|
||||
> 你知道吗?音·创的最早期版本“《我的世界》函数音乐生成器”正是用函数来播放,不过这个版本采取的读入数据的形式大有不同。
|
||||
|
||||
2. 生成结构的方式
|
||||
|
||||
无论是音·创生成的是何种结构,`MCSTRUCTURE`还是`BDX`,都会依照此处的格式来生成。此处我们想说明的结构的格式不是结构文件存储的格式,而是结构导出之后方块摆放的方式。结构文件存储的格式这一点,在各个《我的世界》开发的相关网站上都可能会有说明。
|
||||
|
||||
考虑到进行《我的世界》游戏开发时,为了节约常加载区域,很多游戏会将指令区设立为一种层叠式的结构。这种结构会限制每一层的指令系统的高度,但是虽然长宽也是有限的,却仍然比其纵轴延伸得更加自由。
|
||||
|
||||
所以,结构的生成形状依照给定的高度和内含指令的数量决定。其 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整。用数学公式的方式表达,则是:
|
||||
|
||||
$$ MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor $$
|
||||
|
||||
其中,$MaxZ$ 即生成结构的$Z$轴最大延伸长度,$NoC$ 表示链结构中所含指令方块的个数,$MaxH$ 表示给定的生成结构的最大高度。
|
||||
|
||||
我们的结构生成器在生成指令链时,将首先以相对坐标系 $(0, 0, 0)$ (即相对原点)开始,自下向上堆叠高度轴(即 $Y$ 轴)的长,当高度轴达到了限制的高度时,便将 $Z$ 轴向正方向堆叠 $1$ 个方块,并开始自上向下重新堆叠,直至高度轴坐标达到相对为 $0$。若当所生成结构的 $Z$ 轴长达到了其最大延伸长度,则此结构生成器将反转 $Z$ 轴的堆叠方向,直至 $Z$ 轴坐标相对为 $0$。如此往复,直至指令链堆叠完成。
|
||||
|
||||
|
||||
# 进度条自定义
|
||||
|
||||
因为我们提供了可以自动转换进度条的功能,因此在这里给出进度条自定义参数的详细解释。
|
||||
|
||||
一个进度条,明显地,有**固定部分**和**可变部分**来构成。而可变部分又包括了文字和图形两种(当然,《我的世界》里头的进度条,可变的图形也就是那个“条”了)。这一点你需要了解,因为后文中包含了很多这方面的概念需要你了解。
|
||||
|
||||
进度条的自定义功能使用一个字符串来定义自己的样式,其中包含众多**标识符**来表示可变部分。
|
||||
|
||||
标识符如下(注意大小写):
|
||||
|
||||
| 标识符 | 指定的可变量 |
|
||||
|---------|----------------|
|
||||
| `%%N` | 乐曲名(即传入的文件名)|
|
||||
| `%%s` | 当前计分板值 |
|
||||
| `%^s` | 计分板最大值 |
|
||||
| `%%t` | 当前播放时间 |
|
||||
| `%^t` | 曲目总时长 |
|
||||
| `%%%` | 当前进度比率 |
|
||||
| `_` | 用以表示进度条占位|
|
||||
|
||||
表示进度条占位的 `_` 是用来标识你的进度条的。也就是可变部分的唯一的图形部分。
|
||||
|
||||
**样式定义字符串(基础样式)**的样例如下,这也是默认进度条的基础样式:
|
||||
|
||||
```▶ %%N [ %%s/%^s %%% __________ %%t|%^t]```
|
||||
|
||||
这是单独一行的进度条,当然你也可以制作多行的,如果是一行的,输出时所使用的指令便是 `title`,而如果是多行的话,输出就会用 `titleraw` 作为进度条字幕。
|
||||
|
||||
哦对了,上面的只不过是样式定义,同时还需要定义的是可变图形的部分,也就是进度条上那个真正的“条”。
|
||||
|
||||
对于这个我们就采用了固定参数的方法,对于一个进度条,无非就是“已经播放过的”和“没播放过的”两种形态,例如,我们默认的进度“条”(**可变样式**)的定义是这样的:
|
||||
|
||||
**可变样式甲(已播放样式)**:`'§e=§r'`
|
||||
|
||||
**可变样式乙(未播放样式)**:`'§7=§r'`
|
||||
|
||||
综合起来,把这些参数传给函数需要一个参数整合,使用位于 `Musicreater/subclass.py` 下的 `ProgressBarStyle` 类进行定义:
|
||||
|
||||
我们的默认定义参数如下:
|
||||
|
||||
```python
|
||||
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
|
||||
r"▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]",
|
||||
r"§e=§r",
|
||||
r"§7=§r",
|
||||
)
|
||||
```
|
||||
|
||||
*为了避免生成错误,请尽量避免使用标识符作为定义样式字符串的其他部分*
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<h1 align="center">音·创 Musicreater</h1>
|
||||
|
||||
<p align="center">
|
||||
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
|
||||
</p>
|
||||
|
||||
# 生成文件的使用
|
||||
|
||||
*这是本库所生成文件的使用声明,不是使用本库的教程,若要查看**本库的文档**,可点击[此处](./%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md);若要查看有关文件结构的内容,可以点击[此处](./%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E.md)*
|
||||
|
||||
## 附加包格式
|
||||
|
||||
支持的文件后缀:`.MCPACK`
|
||||
|
||||
- 计分板播放器
|
||||
|
||||
1. 导入附加包
|
||||
2. 在一个循环方块中输入指令 `function index`
|
||||
3. 将需要聆听音乐的实体的播放所用计分板设置为 `1`
|
||||
4. 激活循环方块
|
||||
5. 若想要暂停播放,可以停止循环指令方块的激活状态
|
||||
6. 若想要重置某实体的播放,可以将其播放用的计分板重置
|
||||
7. 若要终止全部玩家的播放,在聊天框输入指令 `function stop`
|
||||
|
||||
> 其中 步骤三 和 步骤四 的顺序可以调换。
|
||||
|
||||
- 延迟播放器
|
||||
|
||||
1. 导入附加包
|
||||
2. 在聊天框输入指令 `function index`
|
||||
3. 同时激活所生成的循环和脉冲指令方块
|
||||
4. 若要终止播放,在聊天框输入指令 `function stop` 试试看,不确保有用
|
||||
|
||||
> 需要注意的是,循环指令方块需要一直激活直到音乐结束
|
||||
|
||||
## 结构格式
|
||||
|
||||
支持的文件后缀:`.MCSTRUCTURE`、`.BDX`
|
||||
|
||||
1. 将结构导入世界
|
||||
|
||||
- 延迟播放器
|
||||
|
||||
2. 将结构生成的第一个指令方块之模式更改为**脉冲**
|
||||
3. 激活脉冲方块
|
||||
4. 若欲重置播放,可以停止对此链的激活,例如停止区块加载
|
||||
5. 此播放器不支持暂停
|
||||
|
||||
- 计分板播放器
|
||||
|
||||
2. 在所生成的第一个指令方块前,放置一个循环指令方块,其朝向应当对着所生成的第一个方块
|
||||
3. 在循环指令方块中输入使播放对象的播放用计分板加分的指令,延迟为 `0`,每次循环增加 `1` 分
|
||||
4. 激活循环方块
|
||||
5. 若想要暂停播放,可以停止循环指令方块的激活状态
|
||||
6. 若想要重置某实体的播放,可以将其播放用的计分板重置
|
||||
|
||||
222
docs/转换乐器对照表.md
222
docs/转换乐器对照表.md
@@ -1,222 +0,0 @@
|
||||
<h1 align="center">音·创 Musicreater</h1>
|
||||
|
||||
<p align="center">
|
||||
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
|
||||
</p>
|
||||
|
||||
# 转换乐器对照表
|
||||
|
||||
**_注意!本文档中的对照表,版权归属于音·创作者,并按照本仓库根目录下 LICENSE.md 中规定开源_**
|
||||
|
||||
原表格请见[constant.py](../Musicreater/constants.py#176)
|
||||
|
||||
**_使用时请遵循协议规定_**
|
||||
|
||||
- 版权所有 © 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
|
||||
|
||||
### 名词解释
|
||||
|
||||
| 名词 | 说明 |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 音符名称 | 我的世界游戏内用于播放音乐的 `playsound` 指令所规定的 `Sound ID` |
|
||||
| 音调偏移参数 | 在《我的世界》中,不同乐器的音域不同,对应的 `pitch` 值也不尽相同,该参数的解释请参考[文档说明](库的生成与功能文档.md#参数释义) |
|
||||
|
||||
# 乐音乐器
|
||||
|
||||
对照表版本:2023 0527
|
||||
|
||||
| Midi 乐器值 | 音符名称 | 音调偏移参数 |
|
||||
| ----------- | ------------------- | ------------ |
|
||||
| 0 | note.harp | 6 |
|
||||
| 1 | note.harp | 6 |
|
||||
| 2 | note.pling | 6 |
|
||||
| 3 | note.harp | 6 |
|
||||
| 4 | note.pling | 6 |
|
||||
| 5 | note.pling | 6 |
|
||||
| 6 | note.harp | 6 |
|
||||
| 7 | note.harp | 6 |
|
||||
| 8 | note.share | 7 |
|
||||
| 9 | note.harp | 6 |
|
||||
| 10 | note.didgeridoo | 8 |
|
||||
| 11 | note.harp | 6 |
|
||||
| 12 | note.xylophone | 4 |
|
||||
| 13 | note.chime | 4 |
|
||||
| 14 | note.harp | 6 |
|
||||
| 15 | note.harp | 6 |
|
||||
| 16 | note.bass | 8 |
|
||||
| 17 | note.harp | 6 |
|
||||
| 18 | note.harp | 6 |
|
||||
| 19 | note.harp | 6 |
|
||||
| 20 | note.harp | 6 |
|
||||
| 21 | note.harp | 6 |
|
||||
| 22 | note.harp | 6 |
|
||||
| 23 | note.guitar | 7 |
|
||||
| 24 | note.guitar | 7 |
|
||||
| 25 | note.guitar | 7 |
|
||||
| 26 | note.guitar | 7 |
|
||||
| 27 | note.guitar | 7 |
|
||||
| 28 | note.guitar | 7 |
|
||||
| 29 | note.guitar | 7 |
|
||||
| 30 | note.guitar | 7 |
|
||||
| 31 | note.bass | 8 |
|
||||
| 32 | note.bass | 8 |
|
||||
| 33 | note.bass | 8 |
|
||||
| 34 | note.bass | 8 |
|
||||
| 35 | note.bass | 8 |
|
||||
| 36 | note.bass | 8 |
|
||||
| 37 | note.bass | 8 |
|
||||
| 38 | note.bass | 8 |
|
||||
| 39 | note.bass | 8 |
|
||||
| 40 | note.harp | 6 |
|
||||
| 41 | note.harp | 6 |
|
||||
| 42 | note.harp | 6 |
|
||||
| 43 | note.harp | 6 |
|
||||
| 44 | note.iron_xylophone | 6 |
|
||||
| 45 | note.guitar | 7 |
|
||||
| 46 | note.harp | 6 |
|
||||
| 47 | note.harp | 6 |
|
||||
| 48 | note.guitar | 7 |
|
||||
| 49 | note.guitar | 7 |
|
||||
| 50 | note.bit | 6 |
|
||||
| 51 | note.bit | 6 |
|
||||
| 52 | note.harp | 6 |
|
||||
| 53 | note.harp | 6 |
|
||||
| 54 | note.bit | 6 |
|
||||
| 55 | note.flute | 5 |
|
||||
| 56 | note.flute | 5 |
|
||||
| 57 | note.flute | 5 |
|
||||
| 58 | note.flute | 5 |
|
||||
| 59 | note.flute | 5 |
|
||||
| 60 | note.flute | 5 |
|
||||
| 61 | note.flute | 5 |
|
||||
| 62 | note.flute | 5 |
|
||||
| 63 | note.flute | 5 |
|
||||
| 64 | note.bit | 6 |
|
||||
| 65 | note.bit | 6 |
|
||||
| 66 | note.bit | 6 |
|
||||
| 67 | note.bit | 6 |
|
||||
| 68 | note.flute | 5 |
|
||||
| 69 | note.harp | 6 |
|
||||
| 70 | note.harp | 6 |
|
||||
| 71 | note.flute | 5 |
|
||||
| 72 | note.flute | 5 |
|
||||
| 73 | note.flute | 5 |
|
||||
| 74 | note.harp | 6 |
|
||||
| 75 | note.flute | 5 |
|
||||
| 76 | note.harp | 6 |
|
||||
| 77 | note.harp | 6 |
|
||||
| 78 | note.harp | 6 |
|
||||
| 79 | note.harp | 6 |
|
||||
| 80 | note.bit | 6 |
|
||||
| 81 | note.bit | 6 |
|
||||
| 82 | note.bit | 6 |
|
||||
| 83 | note.bit | 6 |
|
||||
| 84 | note.bit | 6 |
|
||||
| 85 | note.bit | 6 |
|
||||
| 86 | note.bit | 6 |
|
||||
| 87 | note.bit | 6 |
|
||||
| 88 | note.bit | 6 |
|
||||
| 89 | note.bit | 6 |
|
||||
| 90 | note.bit | 6 |
|
||||
| 91 | note.bit | 6 |
|
||||
| 92 | note.bit | 6 |
|
||||
| 93 | note.bit | 6 |
|
||||
| 94 | note.bit | 6 |
|
||||
| 95 | note.bit | 6 |
|
||||
| 96 | note.bit | 6 |
|
||||
| 97 | note.bit | 6 |
|
||||
| 98 | note.bit | 6 |
|
||||
| 99 | note.bit | 6 |
|
||||
| 100 | note.bit | 6 |
|
||||
| 101 | note.bit | 6 |
|
||||
| 102 | note.bit | 6 |
|
||||
| 103 | note.bit | 6 |
|
||||
| 104 | note.harp | 6 |
|
||||
| 105 | note.banjo | 6 |
|
||||
| 106 | note.harp | 6 |
|
||||
| 107 | note.harp | 6 |
|
||||
| 108 | note.harp | 6 |
|
||||
| 109 | note.harp | 6 |
|
||||
| 110 | note.harp | 6 |
|
||||
| 111 | note.guitar | 7 |
|
||||
| 112 | note.harp | 6 |
|
||||
| 113 | note.bell | 4 |
|
||||
| 114 | note.harp | 6 |
|
||||
| 115 | note.cow_bell | 5 |
|
||||
| 116 | note.bd | 7 |
|
||||
| 117 | note.bass | 8 |
|
||||
| 118 | note.bit | 6 |
|
||||
| 119 | note.bd | 7 |
|
||||
| 120 | note.guitar | 7 |
|
||||
| 121 | note.harp | 6 |
|
||||
| 122 | note.harp | 6 |
|
||||
| 123 | note.harp | 6 |
|
||||
| 124 | note.harp | 6 |
|
||||
| 125 | note.hat | 7 |
|
||||
| 126 | note.bd | 7 |
|
||||
| 127 | note.snare | 7 |
|
||||
|
||||
# 打击乐器
|
||||
|
||||
| Midi 打击乐器值 | 音符名称 | 音调偏移参数 |
|
||||
| --------------- | ------------------- | ------------ |
|
||||
| 34 | note.bd | 7 |
|
||||
| 35 | note.bd | 7 |
|
||||
| 36 | note.hat | 7 |
|
||||
| 37 | note.snare | 7 |
|
||||
| 38 | note.snare | 7 |
|
||||
| 39 | note.snare | 7 |
|
||||
| 40 | note.hat | 7 |
|
||||
| 41 | note.snare | 7 |
|
||||
| 42 | note.hat | 7 |
|
||||
| 43 | note.snare | 7 |
|
||||
| 44 | note.snare | 7 |
|
||||
| 45 | note.bell | 4 |
|
||||
| 46 | note.snare | 7 |
|
||||
| 47 | note.snare | 7 |
|
||||
| 48 | note.bell | 4 |
|
||||
| 49 | note.hat | 7 |
|
||||
| 50 | note.bell | 4 |
|
||||
| 51 | note.bell | 4 |
|
||||
| 52 | note.bell | 4 |
|
||||
| 53 | note.bell | 4 |
|
||||
| 54 | note.bell | 4 |
|
||||
| 55 | note.bell | 4 |
|
||||
| 56 | note.snare | 7 |
|
||||
| 57 | note.hat | 7 |
|
||||
| 58 | note.chime | 4 |
|
||||
| 59 | note.iron_xylophone | 6 |
|
||||
| 60 | note.bd | 7 |
|
||||
| 61 | note.bd | 7 |
|
||||
| 62 | note.xylophone | 4 |
|
||||
| 63 | note.xylophone | 4 |
|
||||
| 64 | note.xylophone | 4 |
|
||||
| 65 | note.hat | 7 |
|
||||
| 66 | note.bell | 4 |
|
||||
| 67 | note.bell | 4 |
|
||||
| 68 | note.hat | 7 |
|
||||
| 69 | note.hat | 7 |
|
||||
| 70 | note.flute | 5 |
|
||||
| 71 | note.flute | 5 |
|
||||
| 72 | note.hat | 7 |
|
||||
| 73 | note.hat | 7 |
|
||||
| 74 | note.xylophone | 4 |
|
||||
| 75 | note.hat | 7 |
|
||||
| 76 | note.hat | 7 |
|
||||
| 77 | note.xylophone | 4 |
|
||||
| 78 | note.xylophone | 4 |
|
||||
| 79 | note.bell | 4 |
|
||||
| 80 | note.bell | 4 |
|
||||
149
docs/音乐序列文件格式.md
149
docs/音乐序列文件格式.md
@@ -1,149 +0,0 @@
|
||||
# 音乐序列文件格式
|
||||
|
||||
音·创 库的音符序列文件格式包含两种,一种是常规的音乐序列存储采用的 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 校验值,以 全曲音符总数 作为种子值。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,265 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
功能测试 若非已知 请勿更改
|
||||
此文件仅供功能测试,并非实际调用的文件
|
||||
请注意,此处的文件均为测试使用
|
||||
不要更改 不要更改 不要更改
|
||||
请注意这里的一切均需要其原作者更改
|
||||
这里用于放置一些新奇的点子
|
||||
用于测试
|
||||
不要更改 不要更改 不要更改!
|
||||
"""
|
||||
|
||||
# 音·创 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 版权所有 金羿("Eilles") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
"""
|
||||
音·创 (Musicreater)
|
||||
是一款免费开源的针对《我的世界》的midi音乐转换库
|
||||
Musicreater (音·创)
|
||||
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
|
||||
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 ../License.md
|
||||
Terms & Conditions: ../License.md
|
||||
"""
|
||||
|
||||
|
||||
# ============================
|
||||
|
||||
|
||||
import mido
|
||||
|
||||
|
||||
class NoteMessage:
|
||||
def __init__(
|
||||
self, channel, pitch, velocity, startT, lastT, midi, now_bpm, change_bpm=None
|
||||
):
|
||||
self.channel = channel
|
||||
self.note = pitch
|
||||
self.velocity = velocity
|
||||
self.startTime = startT
|
||||
self.lastTime = lastT
|
||||
self.tempo = now_bpm # 这里要程序实现获取bpm可以参考我的程序
|
||||
|
||||
def mt2gt(mt, tpb_a, bpm_a):
|
||||
return mt / tpb_a / bpm_a * 60
|
||||
|
||||
self.startTrueTime = mt2gt(
|
||||
self.startTime, midi.ticks_per_beat, self.tempo
|
||||
) # / 20
|
||||
# delete_extra_zero(round_up())
|
||||
if change_bpm is not None:
|
||||
self.lastTrueTime = mt2gt(
|
||||
self.lastTime, midi.ticks_per_beat, change_bpm
|
||||
) # / 20
|
||||
else:
|
||||
self.lastTrueTime = mt2gt(
|
||||
self.lastTime, midi.ticks_per_beat, self.tempo
|
||||
) # / 20
|
||||
# delete_extra_zero(round_up())
|
||||
print((self.startTime * self.tempo) / (midi.ticks_per_beat * 50000))
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
"noteMessage channel="
|
||||
+ str(self.channel)
|
||||
+ " note="
|
||||
+ str(self.note)
|
||||
+ " velocity="
|
||||
+ str(self.velocity)
|
||||
+ " startTime="
|
||||
+ str(self.startTime)
|
||||
+ " lastTime="
|
||||
+ str(self.lastTime)
|
||||
+ " startTrueTime="
|
||||
+ str(self.startTrueTime)
|
||||
+ " lastTrueTime="
|
||||
+ str(self.lastTrueTime)
|
||||
)
|
||||
|
||||
|
||||
def load(mid: mido.MidiFile):
|
||||
|
||||
type_ = [False, False, False] # note_off / note_on+0 / mixed
|
||||
|
||||
is_tempo = False
|
||||
|
||||
# 预检
|
||||
for i, track in enumerate(mid.tracks):
|
||||
for msg in track:
|
||||
# print(msg)
|
||||
if msg.is_meta is not True:
|
||||
if msg.type == "note_on" and msg.velocity == 0:
|
||||
type_[1] = True
|
||||
elif msg.type == "note_off":
|
||||
type_[0] = True
|
||||
if msg.is_meta is True and msg.type == "set_tempo":
|
||||
is_tempo = True
|
||||
|
||||
if is_tempo is not True:
|
||||
raise Exception("这个mid没有可供计算时间的tempo事件")
|
||||
|
||||
if type_[0] is True and type_[1] is True:
|
||||
type_[2] = True
|
||||
type_[1] = False
|
||||
type_[0] = False
|
||||
print(type_)
|
||||
|
||||
bpm = 0
|
||||
recent_change_bpm = 0
|
||||
is_change_bpm = False
|
||||
# 实检
|
||||
for i, track in enumerate(mid.tracks):
|
||||
noteOn = []
|
||||
trackS = []
|
||||
ticks = 0
|
||||
for msg in track:
|
||||
print(msg)
|
||||
ticks += msg.time
|
||||
print(ticks)
|
||||
if msg.is_meta is True and msg.type == "set_tempo":
|
||||
recent_change_bpm = bpm
|
||||
bpm = 60000000 / msg.tempo
|
||||
is_change_bpm = True
|
||||
|
||||
if msg.type == "note_on" and msg.velocity != 0:
|
||||
noteOn.append([msg, msg.note, ticks])
|
||||
if type_[1] is True:
|
||||
if msg.type == "note_on" and msg.velocity == 0:
|
||||
for u in noteOn:
|
||||
index = 0
|
||||
if u[1] == msg.note:
|
||||
lastMessage = u[0]
|
||||
lastTick = u[2]
|
||||
break
|
||||
index += 1
|
||||
print(lastTick)
|
||||
if is_change_bpm and recent_change_bpm != 0:
|
||||
trackS.append(
|
||||
NoteMessage(
|
||||
msg.channel,
|
||||
msg.note,
|
||||
lastMessage.velocity,
|
||||
lastTick,
|
||||
ticks - lastTick,
|
||||
mid,
|
||||
recent_change_bpm,
|
||||
bpm,
|
||||
)
|
||||
)
|
||||
is_change_bpm = False
|
||||
else:
|
||||
trackS.append(
|
||||
NoteMessage(
|
||||
msg.channel,
|
||||
msg.note,
|
||||
lastMessage.velocity,
|
||||
lastTick,
|
||||
ticks - lastTick,
|
||||
mid,
|
||||
bpm,
|
||||
)
|
||||
)
|
||||
# print(noteOn)
|
||||
# print(index)
|
||||
try:
|
||||
noteOn.pop(index)
|
||||
except IndexError:
|
||||
noteOn.pop(index - 1)
|
||||
print(trackS)
|
||||
for j in trackS:
|
||||
print(j)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
load(mido.MidiFile("test.mid"))
|
||||
|
||||
|
||||
# ============================
|
||||
from typing import Literal
|
||||
|
||||
from ..constants import x, y, z
|
||||
|
||||
|
||||
# 不要用 没写完
|
||||
def delay_to_note_blocks(
|
||||
baseblock: str = "stone",
|
||||
position_forward: Literal["x", "y", "z"] = z,
|
||||
):
|
||||
"""传入音符,生成以音符盒存储的红石音乐
|
||||
:param:
|
||||
baseblock: 中继器的下垫方块
|
||||
position_forward: 结构延长方向
|
||||
:return 是否生成成功
|
||||
"""
|
||||
|
||||
from TrimMCStruct import Block, Structure
|
||||
|
||||
struct = Structure(
|
||||
(_sideLength, max_height, _sideLength), # 声明结构大小
|
||||
)
|
||||
|
||||
log = print
|
||||
|
||||
startpos = [0, 0, 0]
|
||||
|
||||
# 1拍 x 2.5 rt
|
||||
for i in notes:
|
||||
error = True
|
||||
try:
|
||||
struct.set_block(
|
||||
[startpos[0], startpos[1] + 1, startpos[2]],
|
||||
form_note_block_in_NBT_struct(height2note[i[0]], instrument),
|
||||
)
|
||||
struct.set_block(
|
||||
startpos,
|
||||
Block("universal_minecraft", instuments[i[0]][1]),
|
||||
)
|
||||
error = False
|
||||
except ValueError:
|
||||
log("无法放置音符:" + str(i) + "于" + str(startpos))
|
||||
struct.set_block(Block("universal_minecraft", baseblock), startpos)
|
||||
struct.set_block(
|
||||
Block("universal_minecraft", baseblock),
|
||||
[startpos[0], startpos[1] + 1, startpos[2]],
|
||||
)
|
||||
finally:
|
||||
if error is True:
|
||||
log("无法放置音符:" + str(i) + "于" + str(startpos))
|
||||
struct.set_block(Block("universal_minecraft", baseblock), startpos)
|
||||
struct.set_block(
|
||||
Block("universal_minecraft", baseblock),
|
||||
[startpos[0], startpos[1] + 1, startpos[2]],
|
||||
)
|
||||
delay = int(i[1] * speed + 0.5)
|
||||
if delay <= 4:
|
||||
startpos[0] += 1
|
||||
struct.set_block(
|
||||
form_repeater_in_NBT_struct(delay, "west"),
|
||||
[startpos[0], startpos[1] + 1, startpos[2]],
|
||||
)
|
||||
struct.set_block(Block("universal_minecraft", baseblock), startpos)
|
||||
else:
|
||||
for j in range(int(delay / 4)):
|
||||
startpos[0] += 1
|
||||
struct.set_block(
|
||||
form_repeater_in_NBT_struct(4, "west"),
|
||||
[startpos[0], startpos[1] + 1, startpos[2]],
|
||||
)
|
||||
struct.set_block(Block("universal_minecraft", baseblock), startpos)
|
||||
if delay % 4 != 0:
|
||||
startpos[0] += 1
|
||||
struct.set_block(
|
||||
form_repeater_in_NBT_struct(delay % 4, "west"),
|
||||
[startpos[0], startpos[1] + 1, startpos[2]],
|
||||
)
|
||||
struct.set_block(Block("universal_minecraft", baseblock), startpos)
|
||||
startpos[0] += posadder[0]
|
||||
startpos[1] += posadder[1]
|
||||
startpos[2] += posadder[2]
|
||||
@@ -1,165 +0,0 @@
|
||||
# -*- 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)
|
||||
@@ -1,144 +0,0 @@
|
||||
# -*- 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,
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
__all__ = [
|
||||
# 通用
|
||||
# "ConvertConfig",
|
||||
"bottem_side_length_of_smallest_square_bottom_box",
|
||||
# 打包
|
||||
"compress_zipfile",
|
||||
"behavior_mcpack_manifest",
|
||||
# MCSTRUCTURE 函数
|
||||
"antiaxis",
|
||||
"forward_IER",
|
||||
"command_statevalue",
|
||||
"form_note_block_in_NBT_struct",
|
||||
"form_repeater_in_NBT_struct",
|
||||
"form_command_block_in_NBT_struct",
|
||||
"commands_to_structure",
|
||||
"commands_to_redstone_delay_structure",
|
||||
# MCSTRUCTURE 常量
|
||||
"AXIS_PARTICULAR_VALUE",
|
||||
"COMPABILITY_VERSION_117",
|
||||
"COMPABILITY_VERSION_119",
|
||||
"COMPABILITY_VERSION_121",
|
||||
# BDX 函数
|
||||
"bdx_move",
|
||||
"form_command_block_in_BDX_bytes",
|
||||
"commands_to_BDX_bytes",
|
||||
# BDX 常量
|
||||
"BDX_MOVE_KEY",
|
||||
]
|
||||
__author__ = (("金羿", "Eilles"), ("诸葛亮与八卦阵", "bgArray"))
|
||||
|
||||
# from .main import ConvertConfig
|
||||
|
||||
from .archive import compress_zipfile, behavior_mcpack_manifest
|
||||
|
||||
from .bdx import (
|
||||
BDX_MOVE_KEY,
|
||||
bdx_move,
|
||||
form_command_block_in_BDX_bytes,
|
||||
commands_to_BDX_bytes,
|
||||
)
|
||||
|
||||
from .common import bottem_side_length_of_smallest_square_bottom_box
|
||||
|
||||
from .mcstructure import (
|
||||
antiaxis,
|
||||
forward_IER,
|
||||
AXIS_PARTICULAR_VALUE,
|
||||
COMPABILITY_VERSION_119,
|
||||
COMPABILITY_VERSION_117,
|
||||
COMPABILITY_VERSION_121,
|
||||
command_statevalue,
|
||||
form_note_block_in_NBT_struct,
|
||||
form_repeater_in_NBT_struct,
|
||||
form_command_block_in_NBT_struct,
|
||||
commands_to_structure,
|
||||
commands_to_redstone_delay_structure,
|
||||
)
|
||||
@@ -1,30 +0,0 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
__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"),)
|
||||
|
||||
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,
|
||||
)
|
||||
@@ -1,684 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,104 +0,0 @@
|
||||
# -*- 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 datetime
|
||||
import os
|
||||
import uuid
|
||||
import zipfile
|
||||
from typing import List, Literal, Union
|
||||
|
||||
|
||||
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
|
||||
"""
|
||||
使用指定的压缩算法将目录打包为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)
|
||||
pre_len = len(os.path.dirname(sourceDir))
|
||||
for parent, dirnames, filenames in os.walk(sourceDir):
|
||||
for filename in filenames:
|
||||
if filename == exceptFile:
|
||||
continue
|
||||
pathfile = os.path.join(parent, filename)
|
||||
arc_name = pathfile[pre_len:].strip(os.path.sep) # 相对路径
|
||||
zipf.write(pathfile, arc_name)
|
||||
zipf.close()
|
||||
|
||||
|
||||
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,
|
||||
):
|
||||
"""
|
||||
生成一个我的世界行为包组件的定义清单文件
|
||||
"""
|
||||
if not pack_version:
|
||||
now_date = datetime.datetime.now()
|
||||
pack_version = [
|
||||
now_date.year,
|
||||
now_date.month * 100 + now_date.day,
|
||||
now_date.hour * 100 + now_date.minute,
|
||||
]
|
||||
result = {
|
||||
"format_version": format_version,
|
||||
"header": {
|
||||
"description": pack_description,
|
||||
"version": pack_version,
|
||||
"name": pack_name,
|
||||
"uuid": str(uuid.uuid4()) if not pack_uuid else pack_uuid,
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"description": modules_description,
|
||||
"type": "data",
|
||||
"version": modules_version,
|
||||
"uuid": str(uuid.uuid4()) if not modules_uuid else modules_uuid,
|
||||
}
|
||||
],
|
||||
}
|
||||
if pack_engine_version:
|
||||
result["header"]["min_engine_version"] = pack_engine_version
|
||||
return result
|
||||
@@ -1,229 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放有关BDX结构操作的内容
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 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 List
|
||||
|
||||
from ..constants import x, y, z
|
||||
from ..subclass import MineCommand
|
||||
from .common import bottem_side_length_of_smallest_square_bottom_box
|
||||
|
||||
BDX_MOVE_KEY = {
|
||||
"x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"],
|
||||
"y": [b"\x11", b"\x10", b"\x1d", b"\x16", b"\x17"],
|
||||
"z": [b"\x13", b"\x12", b"\x1e", b"\x18", b"\x19"],
|
||||
}
|
||||
"""key存储了方块移动指令的数据,其中可以用key[x|y|z][0|1]来表示xyz的减或增
|
||||
而key[][2+]是用来增加指定数目的"""
|
||||
|
||||
|
||||
def bdx_move(axis: str, value: int):
|
||||
if value == 0:
|
||||
return b""
|
||||
if abs(value) == 1:
|
||||
return BDX_MOVE_KEY[axis][0 if value == -1 else 1]
|
||||
|
||||
pointer = sum(
|
||||
[
|
||||
value != -1,
|
||||
value < -1 or value > 1,
|
||||
value < -128 or value > 127,
|
||||
value < -32768 or value > 32767,
|
||||
]
|
||||
)
|
||||
|
||||
return BDX_MOVE_KEY[axis][pointer] + value.to_bytes(
|
||||
2 ** (pointer - 2), "big", signed=True
|
||||
)
|
||||
|
||||
|
||||
def form_command_block_in_BDX_bytes(
|
||||
command: str,
|
||||
particularValue: int,
|
||||
impluse: int = 0,
|
||||
condition: bool = False,
|
||||
needRedstone: bool = True,
|
||||
tickDelay: int = 0,
|
||||
customName: str = "",
|
||||
executeOnFirstTick: bool = False,
|
||||
trackOutput: bool = True,
|
||||
) -> bytes:
|
||||
"""
|
||||
使用指定参数生成指定的指令方块放置指令项
|
||||
|
||||
Parameters
|
||||
------------
|
||||
command: str
|
||||
指令
|
||||
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 下 有条件
|
||||
注意!此处特殊值中的条件会被下面condition参数覆写
|
||||
impluse: int (0|1|2)
|
||||
方块类型
|
||||
0脉冲 1循环 2连锁
|
||||
condition: bool
|
||||
是否有条件
|
||||
needRedstone: bool
|
||||
是否需要红石
|
||||
tickDelay: int
|
||||
执行延时
|
||||
customName: str
|
||||
悬浮字
|
||||
lastOutput: str
|
||||
命令方块的上次输出字符串,注意此处需要留空
|
||||
executeOnFirstTick: bool
|
||||
是否启用首刻执行(循环指令方块是否激活后立即执行,若为False,则从激活时起延迟后第一次执行)
|
||||
trackOutput: bool
|
||||
是否启用命令方块输出
|
||||
|
||||
Returns
|
||||
---------
|
||||
bytes
|
||||
用以生成 bdx 结构的字节码
|
||||
"""
|
||||
block = b"\x24" + particularValue.to_bytes(2, byteorder="big", signed=False)
|
||||
|
||||
for i in [
|
||||
impluse.to_bytes(4, byteorder="big", signed=False),
|
||||
bytes(command, encoding="utf-8") + b"\x00",
|
||||
bytes(customName, encoding="utf-8") + b"\x00",
|
||||
bytes("", encoding="utf-8") + b"\x00",
|
||||
tickDelay.to_bytes(4, byteorder="big", signed=True),
|
||||
executeOnFirstTick.to_bytes(1, byteorder="big"),
|
||||
trackOutput.to_bytes(1, byteorder="big"),
|
||||
condition.to_bytes(1, byteorder="big"),
|
||||
needRedstone.to_bytes(1, byteorder="big"),
|
||||
]:
|
||||
block += i
|
||||
return block
|
||||
|
||||
|
||||
def commands_to_BDX_bytes(
|
||||
commands_list: List[MineCommand],
|
||||
max_height: int = 64,
|
||||
):
|
||||
"""
|
||||
指令列表转换为用以生成 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(
|
||||
len(commands_list), max_height
|
||||
)
|
||||
_bytes = b""
|
||||
|
||||
y_forward = True
|
||||
z_forward = True
|
||||
|
||||
now_y = 0
|
||||
now_z = 0
|
||||
now_x = 0
|
||||
|
||||
for command in commands_list:
|
||||
_bytes += form_command_block_in_BDX_bytes(
|
||||
command.command_text,
|
||||
(
|
||||
(1 if y_forward else 0)
|
||||
if (
|
||||
((now_y != 0) and (not y_forward))
|
||||
or (y_forward and (now_y != (max_height - 1)))
|
||||
)
|
||||
else (
|
||||
(3 if z_forward else 2)
|
||||
if (
|
||||
((now_z != 0) and (not z_forward))
|
||||
or (z_forward and (now_z != _sideLength - 1))
|
||||
)
|
||||
else 5
|
||||
)
|
||||
),
|
||||
impluse=2,
|
||||
condition=command.conditional,
|
||||
needRedstone=False,
|
||||
tickDelay=command.delay,
|
||||
customName=command.annotation_text,
|
||||
executeOnFirstTick=False,
|
||||
trackOutput=True,
|
||||
)
|
||||
|
||||
# (1 if y_forward else 0) if ( # 如果y+则向上,反之向下
|
||||
# ((now_y != 0) and (not y_forward)) # 如果不是y轴上首个方块
|
||||
# or (y_forward and (now_y != (max_height - 1))) # 如果不是y轴上末端方块
|
||||
# ) else ( # 否则,即是y轴末端或首个方块
|
||||
# (3 if z_forward else 2) if ( # 如果z+则向z轴正方向,反之负方向
|
||||
# ((now_z != 0) and (not z_forward)) # 如果不是z轴上的首个方块
|
||||
# or (z_forward and (now_z != _sideLength - 1)) # 如果不是z轴上的末端方块
|
||||
# ) else 5 # 否则,则要面向x轴正方向
|
||||
# )
|
||||
|
||||
now_y += 1 if y_forward else -1
|
||||
|
||||
if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)):
|
||||
now_y -= 1 if y_forward else -1
|
||||
|
||||
y_forward = not y_forward
|
||||
|
||||
now_z += 1 if z_forward else -1
|
||||
|
||||
if ((now_z >= _sideLength) and z_forward) or (
|
||||
(now_z < 0) and (not z_forward)
|
||||
):
|
||||
now_z -= 1 if z_forward else -1
|
||||
z_forward = not z_forward
|
||||
_bytes += BDX_MOVE_KEY[x][1]
|
||||
now_x += 1
|
||||
else:
|
||||
_bytes += BDX_MOVE_KEY[z][int(z_forward)]
|
||||
|
||||
else:
|
||||
_bytes += BDX_MOVE_KEY[y][int(y_forward)]
|
||||
|
||||
return (
|
||||
_bytes,
|
||||
[
|
||||
now_x + 1,
|
||||
max_height if now_x or now_z else now_y,
|
||||
_sideLength if now_x else now_z,
|
||||
],
|
||||
[now_x, now_y, now_z],
|
||||
)
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用以生成BDX结构文件的附加功能
|
||||
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__all__ = ["to_BDX_file_in_score", "to_BDX_file_in_delay"]
|
||||
__author__ = (("金羿", "Eilles"),)
|
||||
|
||||
from .main import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||
@@ -1,220 +0,0 @@
|
||||
# -*- 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 os
|
||||
from typing import Optional
|
||||
|
||||
import brotli
|
||||
|
||||
from ...old_main import MidiConvert
|
||||
from ...subclass import MineCommand, ProgressBarStyle
|
||||
from ..bdx import (
|
||||
bdx_move,
|
||||
commands_to_BDX_bytes,
|
||||
form_command_block_in_BDX_bytes,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
)
|
||||
|
||||
|
||||
def to_BDX_file_in_score(
|
||||
midi_cvt: MidiConvert,
|
||||
dist_path: str,
|
||||
progressbar_style: Optional[ProgressBarStyle],
|
||||
scoreboard_name: str = "mscplay",
|
||||
auto_reset: bool = False,
|
||||
author: str = "Eilles",
|
||||
max_height: int = 64,
|
||||
):
|
||||
"""
|
||||
将midi以计分播放器形式转换为BDX结构文件
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
dist_path: str
|
||||
转换结果输出的目标路径
|
||||
progressbar_style: ProgressBarStyle 对象
|
||||
进度条对象
|
||||
scoreboard_name: str
|
||||
我的世界的计分板名称
|
||||
auto_reset: bool
|
||||
是否自动重置计分板
|
||||
author: str
|
||||
作者名称
|
||||
max_height: int
|
||||
生成结构最大高度
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[int指令数量, int音乐总延迟, tuple[int,]结构大小, tuple[int,]终点坐标]
|
||||
"""
|
||||
|
||||
cmdlist, command_count, max_score = midi_cvt.to_command_list_in_score(
|
||||
scoreboard_name=scoreboard_name,
|
||||
)
|
||||
|
||||
if not os.path.exists(dist_path):
|
||||
os.makedirs(dist_path)
|
||||
|
||||
with open(
|
||||
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[score].bdx")),
|
||||
"w+",
|
||||
) as f:
|
||||
f.write("BD@")
|
||||
|
||||
_bytes = (
|
||||
b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00"
|
||||
)
|
||||
|
||||
cmdBytes, size, finalPos = commands_to_BDX_bytes(
|
||||
midi_cvt.music_command_list
|
||||
+ (
|
||||
[
|
||||
MineCommand(
|
||||
command="scoreboard players reset @a[scores={"
|
||||
+ scoreboard_name
|
||||
+ "="
|
||||
+ str(max_score + 20)
|
||||
+ "}] "
|
||||
+ scoreboard_name,
|
||||
annotation="自动重置计分板",
|
||||
)
|
||||
]
|
||||
if auto_reset
|
||||
else []
|
||||
),
|
||||
max_height - 1,
|
||||
)
|
||||
|
||||
if progressbar_style:
|
||||
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
|
||||
midi_cvt.form_progress_bar(max_score, scoreboard_name, progressbar_style),
|
||||
max_height - 1,
|
||||
)
|
||||
_bytes += pgbBytes
|
||||
_bytes += bdx_move(y, -pgbNowPos[1])
|
||||
_bytes += bdx_move(z, -pgbNowPos[2])
|
||||
_bytes += bdx_move(x, 2)
|
||||
|
||||
size[0] += 2 + pgbSize[0]
|
||||
size[1] = max(size[1], pgbSize[1])
|
||||
size[2] = max(size[2], pgbSize[2])
|
||||
|
||||
_bytes += cmdBytes
|
||||
|
||||
with open(
|
||||
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"))
|
||||
|
||||
return command_count, max_score, size, finalPos
|
||||
|
||||
|
||||
def to_BDX_file_in_delay(
|
||||
midi_cvt: MidiConvert,
|
||||
dist_path: str,
|
||||
progressbar_style: Optional[ProgressBarStyle],
|
||||
player: str = "@a",
|
||||
author: str = "Eilles",
|
||||
max_height: int = 64,
|
||||
):
|
||||
"""
|
||||
使用method指定的转换算法,将midi转换为BDX结构文件
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
dist_path: str
|
||||
转换结果输出的目标路径
|
||||
progressbar_style: ProgressBarStyle 对象
|
||||
进度条对象
|
||||
player: str
|
||||
玩家选择器,默认为`@a`
|
||||
author: str
|
||||
作者名称
|
||||
max_height: int
|
||||
生成结构最大高度
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[int指令数量, int音乐总延迟, tuple[int,]结构大小, tuple[int,]终点坐标]
|
||||
"""
|
||||
|
||||
cmdlist, max_delay = midi_cvt.to_command_list_in_delay(
|
||||
player_selector=player,
|
||||
)[:2]
|
||||
|
||||
if not os.path.exists(dist_path):
|
||||
os.makedirs(dist_path)
|
||||
|
||||
with open(
|
||||
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[delay].bdx")),
|
||||
"w+",
|
||||
) as f:
|
||||
f.write("BD@")
|
||||
|
||||
_bytes = (
|
||||
b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00"
|
||||
)
|
||||
|
||||
cmdBytes, size, finalPos = commands_to_BDX_bytes(cmdlist, max_height - 1)
|
||||
|
||||
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,
|
||||
customName="初始化进度条",
|
||||
)
|
||||
_bytes += bdx_move(z, 2)
|
||||
_bytes += form_command_block_in_BDX_bytes(
|
||||
r"scoreboard players add {} {} 1".format(player, scb_name),
|
||||
1,
|
||||
1,
|
||||
customName="显示进度条并加分",
|
||||
)
|
||||
_bytes += bdx_move(y, 1)
|
||||
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
|
||||
midi_cvt.form_progress_bar(max_delay, scb_name, progressbar_style),
|
||||
max_height - 1,
|
||||
)
|
||||
_bytes += pgbBytes
|
||||
_bytes += bdx_move(y, -1 - pgbNowPos[1])
|
||||
_bytes += bdx_move(z, -2 - pgbNowPos[2])
|
||||
_bytes += bdx_move(x, 2)
|
||||
_bytes += form_command_block_in_BDX_bytes(
|
||||
r"scoreboard players reset {} {}".format(player, scb_name),
|
||||
1,
|
||||
customName="置零进度条",
|
||||
)
|
||||
_bytes += bdx_move(y, 1)
|
||||
size[0] += 2 + pgbSize[0]
|
||||
size[1] = max(size[1], pgbSize[1])
|
||||
size[2] = max(size[2], pgbSize[2])
|
||||
|
||||
size[1] += 1
|
||||
_bytes += cmdBytes
|
||||
|
||||
with open(
|
||||
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"))
|
||||
|
||||
return len(cmdlist), max_delay, size, finalPos
|
||||
@@ -1,40 +0,0 @@
|
||||
# -*- 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)))
|
||||
@@ -1,92 +0,0 @@
|
||||
# -*- 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
|
||||
# """进度条样式组"""
|
||||
@@ -1,30 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用以生成单个mcstructure文件的附加功能
|
||||
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__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"),)
|
||||
|
||||
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,298 +0,0 @@
|
||||
# -*- 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 os
|
||||
from typing import Literal
|
||||
|
||||
from ...old_main import MidiConvert
|
||||
from ...subclass import MineCommand
|
||||
from ..mcstructure import (
|
||||
COMPABILITY_VERSION_117,
|
||||
COMPABILITY_VERSION_119,
|
||||
commands_to_redstone_delay_structure,
|
||||
commands_to_structure,
|
||||
)
|
||||
|
||||
|
||||
def to_mcstructure_file_in_delay(
|
||||
midi_cvt: MidiConvert,
|
||||
dist_path: str,
|
||||
player: str = "@a",
|
||||
max_height: int = 64,
|
||||
):
|
||||
"""
|
||||
将midi以延迟播放器形式转换为mcstructure结构文件
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
dist_path: str
|
||||
转换结果输出的目标路径
|
||||
player: str
|
||||
玩家选择器,默认为`@a`
|
||||
max_height: int
|
||||
生成结构最大高度
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[tuple[int,int,int]结构大小, int音乐总延迟]
|
||||
"""
|
||||
|
||||
compability_ver = (
|
||||
COMPABILITY_VERSION_117
|
||||
if midi_cvt.enable_old_exe_format
|
||||
else COMPABILITY_VERSION_119
|
||||
)
|
||||
|
||||
cmd_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)
|
||||
|
||||
struct, size, end_pos = commands_to_structure(
|
||||
cmd_list, max_height - 1, compability_version_=compability_ver
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[delay].mcstructure")),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct.dump(f)
|
||||
|
||||
return size, max_delay
|
||||
|
||||
|
||||
def to_mcstructure_file_in_score(
|
||||
midi_cvt: MidiConvert,
|
||||
dist_path: str,
|
||||
scoreboard_name: str = "mscplay",
|
||||
auto_reset: bool = False,
|
||||
max_height: int = 64,
|
||||
):
|
||||
"""
|
||||
将midi以延迟播放器形式转换为mcstructure结构文件
|
||||
|
||||
Parameters
|
||||
----------
|
||||
midi_cvt: MidiConvert 对象
|
||||
用于转换的MidiConvert对象
|
||||
dist_path: str
|
||||
转换结果输出的目标路径
|
||||
scoreboard_name: str
|
||||
我的世界的计分板名称
|
||||
auto_reset: bool
|
||||
是否自动重置计分板
|
||||
max_height: int
|
||||
生成结构最大高度
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[tuple[int,int,int]结构大小, int音乐总延迟, int指令数量
|
||||
"""
|
||||
|
||||
compability_ver = (
|
||||
COMPABILITY_VERSION_117
|
||||
if midi_cvt.enable_old_exe_format
|
||||
else COMPABILITY_VERSION_119
|
||||
)
|
||||
|
||||
cmd_list, cmd_count, max_delay = midi_cvt.to_command_list_in_score(
|
||||
scoreboard_name=scoreboard_name,
|
||||
)
|
||||
|
||||
if not os.path.exists(dist_path):
|
||||
os.makedirs(dist_path)
|
||||
|
||||
struct, size, end_pos = commands_to_structure(
|
||||
midi_cvt.music_command_list
|
||||
+ (
|
||||
[
|
||||
MineCommand(
|
||||
command="scoreboard players reset @a[scores={"
|
||||
+ scoreboard_name
|
||||
+ "="
|
||||
+ str(max_delay + 20)
|
||||
+ "}] "
|
||||
+ scoreboard_name,
|
||||
annotation="自动重置计分板",
|
||||
)
|
||||
]
|
||||
if auto_reset
|
||||
else []
|
||||
),
|
||||
max_height - 1,
|
||||
compability_version_=compability_ver,
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[score].mcstructure")),
|
||||
"wb+",
|
||||
) as f:
|
||||
struct.dump(f)
|
||||
|
||||
return size, max_delay, cmd_count
|
||||
|
||||
|
||||
def to_mcstructure_file_in_repeater(
|
||||
midi_cvt: MidiConvert,
|
||||
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
|
||||
-------
|
||||
tuple[tuple[int,int,int]结构大小, int音乐总延迟]
|
||||
"""
|
||||
|
||||
compability_ver = (
|
||||
COMPABILITY_VERSION_117
|
||||
if midi_cvt.enable_old_exe_format
|
||||
else COMPABILITY_VERSION_119
|
||||
)
|
||||
|
||||
cmd_list, max_delay, max_multiple_cmd = midi_cvt.to_command_list_in_delay(
|
||||
player_selector=player,
|
||||
)
|
||||
|
||||
if not os.path.exists(dist_path):
|
||||
os.makedirs(dist_path)
|
||||
|
||||
struct, size, end_pos = commands_to_redstone_delay_structure(
|
||||
cmd_list,
|
||||
max_delay,
|
||||
max_multiple_cmd,
|
||||
basement_block,
|
||||
axis_side,
|
||||
compability_version_=compability_ver,
|
||||
)
|
||||
|
||||
with open(
|
||||
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
|
||||
@@ -1,543 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放有关MCSTRUCTURE结构操作的内容
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 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 List, Literal, Tuple
|
||||
|
||||
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
|
||||
|
||||
from ..constants import x, y, z
|
||||
from ..subclass import MineCommand
|
||||
from .common import bottem_side_length_of_smallest_square_bottom_box
|
||||
|
||||
|
||||
def antiaxis(axis: Literal["x", "z", "X", "Z"]):
|
||||
return z if axis == x else x
|
||||
|
||||
|
||||
def forward_IER(forward: bool):
|
||||
return 1 if forward else -1
|
||||
|
||||
|
||||
AXIS_PARTICULAR_VALUE = {
|
||||
x: {
|
||||
True: 5,
|
||||
False: 4,
|
||||
},
|
||||
y: {
|
||||
True: 1,
|
||||
False: 0,
|
||||
},
|
||||
z: {
|
||||
True: 3,
|
||||
False: 2,
|
||||
},
|
||||
}
|
||||
|
||||
# 1.19的结构兼容版本号
|
||||
COMPABILITY_VERSION_119: int = 17959425
|
||||
"""
|
||||
Minecraft 1.19 兼容版本号
|
||||
"""
|
||||
# 1.17的结构兼容版本号
|
||||
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):
|
||||
return AXIS_PARTICULAR_VALUE[axis_.lower()][forward_]
|
||||
|
||||
|
||||
def form_note_block_in_NBT_struct(
|
||||
note: int,
|
||||
coordinate: Tuple[int, int, int],
|
||||
instrument: str = "note.harp",
|
||||
powered: bool = False,
|
||||
compability_version_number: int = COMPABILITY_VERSION_119,
|
||||
) -> Block:
|
||||
"""
|
||||
生成音符盒方块
|
||||
|
||||
Parameters
|
||||
------------
|
||||
note: int (0~24)
|
||||
音符的音高
|
||||
coordinate: tuple[int, int, int]
|
||||
此方块所在之相对坐标
|
||||
instrument: str
|
||||
音符盒的乐器
|
||||
powered: bool
|
||||
是否已被激活
|
||||
|
||||
Returns
|
||||
-------
|
||||
Block
|
||||
生成的方块对象
|
||||
"""
|
||||
|
||||
return Block(
|
||||
"minecraft",
|
||||
"noteblock",
|
||||
{
|
||||
"instrument": instrument.replace("note.", ""),
|
||||
"note": note,
|
||||
"powered": powered,
|
||||
},
|
||||
{
|
||||
"block_entity_data": {
|
||||
"note": TAG_Byte(note),
|
||||
"id": "noteblock",
|
||||
"x": coordinate[0],
|
||||
"y": coordinate[1],
|
||||
"z": coordinate[2],
|
||||
} # type: ignore
|
||||
},
|
||||
compability_version=compability_version_number,
|
||||
)
|
||||
|
||||
|
||||
def form_repeater_in_NBT_struct(
|
||||
delay: int,
|
||||
facing: int,
|
||||
compability_version_number: int = COMPABILITY_VERSION_119,
|
||||
) -> Block:
|
||||
"""
|
||||
生成中继器方块
|
||||
|
||||
Parameters
|
||||
----------
|
||||
facing: int (0~3)
|
||||
朝向:
|
||||
Z- 北 0
|
||||
X- 东 1
|
||||
Z+ 南 2
|
||||
X+ 西 3
|
||||
delay: int (0~3)
|
||||
信号延迟
|
||||
|
||||
Returns
|
||||
-------
|
||||
Block
|
||||
生成的方块对象
|
||||
"""
|
||||
|
||||
return Block(
|
||||
"minecraft",
|
||||
"unpowered_repeater",
|
||||
{
|
||||
"repeater_delay": delay,
|
||||
"direction": facing,
|
||||
},
|
||||
compability_version=compability_version_number,
|
||||
)
|
||||
|
||||
|
||||
def form_command_block_in_NBT_struct(
|
||||
command: str,
|
||||
coordinate: tuple,
|
||||
particularValue: int,
|
||||
impluse: int = 0,
|
||||
condition: bool = False,
|
||||
alwaysRun: bool = True,
|
||||
tickDelay: int = 0,
|
||||
customName: str = "",
|
||||
executeOnFirstTick: bool = False,
|
||||
trackOutput: bool = True,
|
||||
compability_version_number: int = COMPABILITY_VERSION_119,
|
||||
) -> Block:
|
||||
"""
|
||||
使用指定参数生成指令方块
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command: str
|
||||
指令
|
||||
coordinate: tuple[int,int,int]
|
||||
此方块所在之相对坐标
|
||||
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 下 有条件
|
||||
注意!此处特殊值中的条件会被下面condition参数覆写
|
||||
impluse: int (0|1|2)
|
||||
方块类型
|
||||
0脉冲 1循环 2连锁
|
||||
condition: bool
|
||||
是否有条件
|
||||
alwaysRun: bool
|
||||
是否始终执行
|
||||
tickDelay: int
|
||||
执行延时
|
||||
customName: str
|
||||
悬浮字
|
||||
executeOnFirstTick: bool
|
||||
是否启用首刻执行(循环指令方块是否激活后立即执行,若为False,则从激活时起延迟后第一次执行)
|
||||
trackOutput: bool
|
||||
是否启用命令方块输出
|
||||
compability_version_number: int
|
||||
版本兼容代号
|
||||
|
||||
Returns
|
||||
-------
|
||||
Block
|
||||
生成的方块对象
|
||||
"""
|
||||
|
||||
return Block(
|
||||
"minecraft",
|
||||
(
|
||||
"command_block"
|
||||
if impluse == 0
|
||||
else ("repeating_command_block" if impluse == 1 else "chain_command_block")
|
||||
),
|
||||
states={"conditional_bit": condition, "facing_direction": particularValue},
|
||||
extra_data={
|
||||
"block_entity_data": {
|
||||
"Command": command,
|
||||
"CustomName": customName,
|
||||
"ExecuteOnFirstTick": executeOnFirstTick,
|
||||
"LPCommandMode": 0,
|
||||
"LPCondionalMode": False,
|
||||
"LPRedstoneMode": False,
|
||||
"LastExecution": TAG_Long(0),
|
||||
"LastOutput": "",
|
||||
"LastOutputParams": [],
|
||||
"SuccessCount": 0,
|
||||
"TickDelay": tickDelay,
|
||||
"TrackOutput": trackOutput,
|
||||
"Version": (
|
||||
25 if compability_version_number <= COMPABILITY_VERSION_119 else 43
|
||||
),
|
||||
"auto": alwaysRun,
|
||||
"conditionMet": False, # 是否已经满足条件
|
||||
"conditionalMode": condition,
|
||||
"id": "CommandBlock",
|
||||
"isMovable": True,
|
||||
"powered": False, # 是否已激活
|
||||
"x": coordinate[0],
|
||||
"y": coordinate[1],
|
||||
"z": coordinate[2],
|
||||
} # type: ignore
|
||||
},
|
||||
compability_version=compability_version_number,
|
||||
)
|
||||
|
||||
|
||||
def commands_to_structure(
|
||||
commands: List[MineCommand],
|
||||
max_height: int = 64,
|
||||
compability_version_: int = COMPABILITY_VERSION_119,
|
||||
):
|
||||
"""
|
||||
由指令列表生成(纯指令方块)结构
|
||||
|
||||
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(
|
||||
len(commands), max_height
|
||||
)
|
||||
|
||||
struct = Structure(
|
||||
size=(_sideLength, max_height, _sideLength), # 声明结构大小
|
||||
compability_version=compability_version_,
|
||||
)
|
||||
|
||||
y_forward = True
|
||||
z_forward = True
|
||||
|
||||
now_y = 0
|
||||
now_z = 0
|
||||
now_x = 0
|
||||
|
||||
for command in commands:
|
||||
coordinate = (now_x, now_y, now_z)
|
||||
struct.set_block(
|
||||
coordinate,
|
||||
form_command_block_in_NBT_struct(
|
||||
command=command.command_text,
|
||||
coordinate=coordinate,
|
||||
particularValue=(
|
||||
(1 if y_forward else 0)
|
||||
if (
|
||||
((now_y != 0) and (not y_forward))
|
||||
or (y_forward and (now_y != (max_height - 1)))
|
||||
)
|
||||
else (
|
||||
(3 if z_forward else 2)
|
||||
if (
|
||||
((now_z != 0) and (not z_forward))
|
||||
or (z_forward and (now_z != _sideLength - 1))
|
||||
)
|
||||
else 5
|
||||
)
|
||||
),
|
||||
impluse=2,
|
||||
condition=False,
|
||||
alwaysRun=True,
|
||||
tickDelay=command.delay,
|
||||
customName=command.annotation_text,
|
||||
executeOnFirstTick=False,
|
||||
trackOutput=True,
|
||||
compability_version_number=compability_version_,
|
||||
),
|
||||
)
|
||||
|
||||
now_y += 1 if y_forward else -1
|
||||
|
||||
if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)):
|
||||
now_y -= 1 if y_forward else -1
|
||||
|
||||
y_forward = not y_forward
|
||||
|
||||
now_z += 1 if z_forward else -1
|
||||
|
||||
if ((now_z >= _sideLength) and z_forward) or (
|
||||
(now_z < 0) and (not z_forward)
|
||||
):
|
||||
now_z -= 1 if z_forward else -1
|
||||
z_forward = not z_forward
|
||||
now_x += 1
|
||||
|
||||
return (
|
||||
struct,
|
||||
(
|
||||
now_x + 1,
|
||||
max_height if now_x or now_z else now_y,
|
||||
_sideLength if now_x else now_z,
|
||||
),
|
||||
(now_x, now_y, now_z),
|
||||
)
|
||||
|
||||
|
||||
def commands_to_redstone_delay_structure(
|
||||
commands: List[MineCommand],
|
||||
delay_length: int,
|
||||
max_multicmd_length: int,
|
||||
base_block: str = "concrete",
|
||||
axis_: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
|
||||
compability_version_: int = COMPABILITY_VERSION_119,
|
||||
) -> Tuple[Structure, Tuple[int, int, int], Tuple[int, int, int]]:
|
||||
"""
|
||||
由指令列表生成由红石中继器延迟的结构
|
||||
|
||||
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
|
||||
aside_direction = x
|
||||
repeater_facing = 2
|
||||
forward = True
|
||||
elif axis_ in ["z-", "Z-"]:
|
||||
extensioon_direction = z
|
||||
aside_direction = x
|
||||
repeater_facing = 0
|
||||
forward = False
|
||||
elif axis_ in ["x+", "X+"]:
|
||||
extensioon_direction = x
|
||||
aside_direction = z
|
||||
repeater_facing = 3
|
||||
forward = True
|
||||
elif axis_ in ["x-", "X-"]:
|
||||
extensioon_direction = x
|
||||
aside_direction = z
|
||||
repeater_facing = 1
|
||||
forward = False
|
||||
else:
|
||||
raise ValueError(f"axis_({axis_}) 参数错误。")
|
||||
|
||||
goahead = forward_IER(forward)
|
||||
|
||||
command_actually_length = sum([int(bool(cmd.delay)) for cmd in commands])
|
||||
|
||||
a = 1
|
||||
a_max = 0
|
||||
total_cmd = 0
|
||||
for cmd in commands:
|
||||
# print("\r 正在进行处理:",end="")
|
||||
if cmd.delay > 2:
|
||||
a_max = max(a, a_max)
|
||||
total_cmd += (a := 1)
|
||||
else:
|
||||
a += 1
|
||||
|
||||
struct = Structure(
|
||||
size=(
|
||||
round(delay_length / 2 + total_cmd) if extensioon_direction == x else a_max,
|
||||
3,
|
||||
round(delay_length / 2 + total_cmd) if extensioon_direction == z else a_max,
|
||||
),
|
||||
fill=Block("minecraft", "air", compability_version=compability_version_),
|
||||
compability_version=compability_version_,
|
||||
)
|
||||
|
||||
pos_now = {
|
||||
x: ((1 if extensioon_direction == x else 0) if forward else struct.size[0]),
|
||||
y: 0,
|
||||
z: ((1 if extensioon_direction == z else 0) if forward else struct.size[2]),
|
||||
}
|
||||
|
||||
chain_list = 0
|
||||
# print("结构元信息设定完毕")
|
||||
|
||||
for cmd in commands:
|
||||
# print("\r 正在进行处理:",end="")
|
||||
if cmd.delay > 1:
|
||||
# print("\rdelay > 0",end='')
|
||||
single_repeater_value = int(cmd.delay / 2) % 4 - 1
|
||||
additional_repeater = int(cmd.delay / 2 // 4)
|
||||
for i in range(additional_repeater):
|
||||
struct.set_block(
|
||||
tuple(pos_now.values()), # type: ignore
|
||||
Block(
|
||||
"minecraft",
|
||||
base_block,
|
||||
compability_version=compability_version_,
|
||||
),
|
||||
)
|
||||
struct.set_block(
|
||||
(pos_now[x], 1, pos_now[z]),
|
||||
form_repeater_in_NBT_struct(
|
||||
delay=3,
|
||||
facing=repeater_facing,
|
||||
compability_version_number=compability_version_,
|
||||
),
|
||||
)
|
||||
pos_now[extensioon_direction] += goahead
|
||||
if single_repeater_value >= 0:
|
||||
struct.set_block(
|
||||
tuple(pos_now.values()), # type: ignore
|
||||
Block(
|
||||
"minecraft",
|
||||
base_block,
|
||||
compability_version=compability_version_,
|
||||
),
|
||||
)
|
||||
struct.set_block(
|
||||
(pos_now[x], 1, pos_now[z]),
|
||||
form_repeater_in_NBT_struct(
|
||||
delay=single_repeater_value,
|
||||
facing=repeater_facing,
|
||||
compability_version_number=compability_version_,
|
||||
),
|
||||
)
|
||||
pos_now[extensioon_direction] += goahead
|
||||
struct.set_block(
|
||||
(pos_now[x], 1, pos_now[z]),
|
||||
form_command_block_in_NBT_struct(
|
||||
command=cmd.command_text,
|
||||
coordinate=(pos_now[x], 1, pos_now[z]),
|
||||
particularValue=command_statevalue(extensioon_direction, forward),
|
||||
# impluse= (0 if first_impluse else 2),
|
||||
impluse=0,
|
||||
condition=False,
|
||||
alwaysRun=False,
|
||||
tickDelay=cmd.delay % 2,
|
||||
customName=cmd.annotation_text,
|
||||
compability_version_number=compability_version_,
|
||||
),
|
||||
)
|
||||
struct.set_block(
|
||||
(pos_now[x], 2, pos_now[z]),
|
||||
Block(
|
||||
"minecraft",
|
||||
"redstone_wire",
|
||||
compability_version=compability_version_,
|
||||
),
|
||||
)
|
||||
pos_now[extensioon_direction] += goahead
|
||||
chain_list = 1
|
||||
|
||||
else:
|
||||
# print(pos_now)
|
||||
now_pos_copy = pos_now.copy()
|
||||
now_pos_copy[extensioon_direction] -= goahead
|
||||
now_pos_copy[aside_direction] += chain_list
|
||||
# print(pos_now,"\n=========")
|
||||
struct.set_block(
|
||||
(now_pos_copy[x], 1, now_pos_copy[z]),
|
||||
form_command_block_in_NBT_struct(
|
||||
command=cmd.command_text,
|
||||
coordinate=(now_pos_copy[x], 1, now_pos_copy[z]),
|
||||
particularValue=command_statevalue(extensioon_direction, forward),
|
||||
# impluse= (0 if first_impluse else 2),
|
||||
impluse=0,
|
||||
condition=False,
|
||||
alwaysRun=False,
|
||||
tickDelay=cmd.delay % 2,
|
||||
customName=cmd.annotation_text,
|
||||
compability_version_number=compability_version_,
|
||||
),
|
||||
)
|
||||
struct.set_block(
|
||||
(now_pos_copy[x], 2, now_pos_copy[z]),
|
||||
Block(
|
||||
"minecraft",
|
||||
"redstone_wire",
|
||||
compability_version=compability_version_,
|
||||
),
|
||||
)
|
||||
chain_list += 1
|
||||
|
||||
return struct, struct.size, tuple(pos_now.values()) # type: ignore
|
||||
@@ -1,117 +0,0 @@
|
||||
# -*- 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 ..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
|
||||
|
||||
# 你以为写完了吗?其实并没有
|
||||
|
||||
|
||||
def to_note_list(
|
||||
midi_cvt: MidiConvert,
|
||||
speed: float = 1.0,
|
||||
) -> list:
|
||||
"""
|
||||
使用金羿的转换思路,将midi转换为我的世界音符盒所用的音高列表,并输出每个音符之后的延迟
|
||||
|
||||
Parameters
|
||||
----------
|
||||
speed: float
|
||||
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple( list[tuple(str指令, int距离上一个指令的延迟 ),...], int音乐时长游戏刻 )
|
||||
"""
|
||||
|
||||
if speed == 0:
|
||||
raise ZeroSpeedError("播放速度仅可为正实数")
|
||||
|
||||
midi_channels = (
|
||||
midi_cvt.to_music_channels() if not midi_cvt.channels else midi_cvt.channels
|
||||
)
|
||||
|
||||
tracks = {}
|
||||
|
||||
# 此处 我们把通道视为音轨
|
||||
for i in midi_channels.keys():
|
||||
# 如果当前通道为空 则跳过
|
||||
if not midi_channels[i]:
|
||||
continue
|
||||
|
||||
# 第十通道是打击乐通道
|
||||
SpecialBits = True if i == 9 else False
|
||||
|
||||
# nowChannel = []
|
||||
|
||||
for track_no, track in midi_channels[i].items():
|
||||
for msg in track:
|
||||
if msg[0] == "PgmC":
|
||||
InstID = msg[1]
|
||||
|
||||
elif msg[0] == "NoteS":
|
||||
try:
|
||||
soundID, _X = (
|
||||
perc_inst_to_soundID_withX(InstID)
|
||||
if SpecialBits
|
||||
else inst_to_sould_with_deviation(InstID)
|
||||
)
|
||||
except UnboundLocalError as E:
|
||||
soundID, _X = (
|
||||
perc_inst_to_soundID_withX(-1)
|
||||
if SpecialBits
|
||||
else inst_to_sould_with_deviation(-1)
|
||||
)
|
||||
score_now = round(msg[-1] / float(speed) / 50)
|
||||
# print(score_now)
|
||||
|
||||
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()
|
||||
results = []
|
||||
|
||||
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,18 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
存放有关Schematic结构生成的内容
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
# import nbtschematic
|
||||
@@ -1,22 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用以生成Schematic结构的附加功能
|
||||
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__all__ = [
|
||||
]
|
||||
__author__ = (("金羿", "Eilles"),)
|
||||
|
||||
from .main import *
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# -*- 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 ..schematic import *
|
||||
@@ -1,21 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用以启动WebSocket服务器播放的附加功能
|
||||
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__all__ = []
|
||||
__author__ = (("金羿", "Eilles"),)
|
||||
|
||||
from .main import *
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
# -*- 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())
|
||||
@@ -1,73 +0,0 @@
|
||||
# -*- 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,],
|
||||
]
|
||||
|
||||
|
||||
@@ -1,861 +0,0 @@
|
||||
# -*- 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"=",
|
||||
)
|
||||
"""
|
||||
默认的进度条样式
|
||||
"""
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
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,68 +0,0 @@
|
||||
import Musicreater.old_init as old_init
|
||||
import Musicreater.experiment
|
||||
import Musicreater.old_plugin
|
||||
|
||||
# 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.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,
|
||||
)
|
||||
|
||||
MSCT_MAIN = (
|
||||
old_init,
|
||||
old_init.experiment,
|
||||
# Musicreater.previous,
|
||||
)
|
||||
|
||||
MSCT_PLUGIN = (old_init.old_plugin,)
|
||||
|
||||
MSCT_PLUGIN_FUNCTION = (
|
||||
to_addon_pack_in_delay,
|
||||
to_addon_pack_in_repeater,
|
||||
to_addon_pack_in_score,
|
||||
to_mcstructure_file_in_delay,
|
||||
to_mcstructure_file_in_repeater,
|
||||
to_mcstructure_file_in_score,
|
||||
to_BDX_file_in_delay,
|
||||
to_BDX_file_in_score,
|
||||
)
|
||||
|
||||
import hashlib
|
||||
|
||||
import brotli
|
||||
import dill
|
||||
|
||||
|
||||
def enpack_msct_pack(sth, to_dist: str):
|
||||
packing_bytes = brotli.compress(
|
||||
dill.dumps(
|
||||
sth,
|
||||
)
|
||||
)
|
||||
with open(
|
||||
to_dist,
|
||||
"wb",
|
||||
) as f:
|
||||
f.write(packing_bytes)
|
||||
|
||||
return hashlib.sha256(packing_bytes)
|
||||
|
||||
|
||||
with open("./Packer/checksum.txt", "w", encoding="utf-8") as f:
|
||||
f.write("MSCT_MAIN:\n")
|
||||
f.write(enpack_msct_pack(MSCT_MAIN, "./Packer/MSCT_MAIN.MPK").hexdigest())
|
||||
f.write("\nMSCT_PLUGIN:\n")
|
||||
f.write(enpack_msct_pack(MSCT_PLUGIN, "./Packer/MSCT_PLUGIN.MPK").hexdigest())
|
||||
f.write("\nMSCT_PLUGIN_FUNCTION:\n")
|
||||
f.write(
|
||||
enpack_msct_pack(
|
||||
MSCT_PLUGIN_FUNCTION, "./Packer/MSCT_PLUGIN_FUNCTION.MPK"
|
||||
).hexdigest()
|
||||
)
|
||||
@@ -1,6 +0,0 @@
|
||||
MSCT_MAIN:
|
||||
6b9f5a97d50beb07c834e375104c67ae44c57ae40f73fb71075b3668899029c7
|
||||
MSCT_PLUGIN:
|
||||
c280413a394a539438a5d10078c9b55f04bcd4cf6869c59a3f7a026039748cfc
|
||||
MSCT_PLUGIN_FUNCTION:
|
||||
40697f1d9b293268fe142fa3e9bffee2923a8f4811ec7bbdf7b14afb98723ef2
|
||||
@@ -1,67 +0,0 @@
|
||||
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)
|
||||
@@ -1,40 +0,0 @@
|
||||
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
|
||||
@@ -1,130 +0,0 @@
|
||||
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",
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
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])
|
||||
@@ -1,134 +0,0 @@
|
||||
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",
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,147 +0,0 @@
|
||||
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()
|
||||
@@ -1,248 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 伶伦 开发交流群 861684859
|
||||
|
||||
|
||||
"""
|
||||
音·创 (Musicreater) 演示程序
|
||||
是一款免费开源的针对《我的世界》的midi音乐转换库
|
||||
Musicreater (音·创)
|
||||
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
|
||||
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
开源相关声明请见 ./License.md
|
||||
Terms & Conditions: ./License.md
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
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.old_plugin.mcstructfile import (
|
||||
to_mcstructure_file_in_delay,
|
||||
to_mcstructure_file_in_repeater,
|
||||
to_mcstructure_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路径:")
|
||||
|
||||
|
||||
# 获取输出地址
|
||||
out_path = input(f"请输入输出路径:")
|
||||
|
||||
|
||||
# 选择输出格式
|
||||
fileFormat = int(
|
||||
input(f"请输入输出格式[MCSTRUCTURE(2) 或 BDX(1) 或 MCPACK(0)]:").lower()
|
||||
)
|
||||
playerFormat = int(input(f"请选择播放方式[红石(2) 或 计分板(1) 或 延迟(0)]:").lower())
|
||||
|
||||
|
||||
# 真假字符串判断
|
||||
def bool_str(sth: str):
|
||||
try:
|
||||
return bool(float(sth))
|
||||
except:
|
||||
if str(sth).lower() in ("true", "真", "是", "y", "t"):
|
||||
return True
|
||||
elif str(sth).lower() in ("false", "假", "否", "f", "n"):
|
||||
return False
|
||||
else:
|
||||
raise ValueError("非法逻辑字串")
|
||||
|
||||
|
||||
def isin(sth: str, range_list: dict):
|
||||
sth = sth.lower()
|
||||
for bool_value, res_list in range_list.items():
|
||||
if sth in res_list:
|
||||
return bool_value
|
||||
raise ValueError(
|
||||
"不在可选范围内:{}".format([j for i in range_list.values() for j in i])
|
||||
)
|
||||
|
||||
|
||||
if os.path.exists("./demo_config.json"):
|
||||
import json
|
||||
|
||||
prompts = json.load(open("./demo_config.json", "r", encoding="utf-8"))
|
||||
else:
|
||||
prompts = []
|
||||
# 提示语 检测函数 错误提示语
|
||||
for args in [
|
||||
(
|
||||
f"最小播放音量:",
|
||||
float,
|
||||
),
|
||||
(
|
||||
f"播放速度:",
|
||||
float,
|
||||
),
|
||||
(
|
||||
f"是否启用进度条:",
|
||||
bool_str,
|
||||
),
|
||||
(
|
||||
(
|
||||
f"计分板名称:",
|
||||
str,
|
||||
)
|
||||
if playerFormat == 1
|
||||
else (
|
||||
f"玩家选择器:",
|
||||
str,
|
||||
)
|
||||
),
|
||||
(
|
||||
(
|
||||
f"是否自动重置计分板:",
|
||||
bool_str,
|
||||
)
|
||||
if playerFormat == 1
|
||||
else ()
|
||||
),
|
||||
(
|
||||
(
|
||||
f"BDX作者署名:",
|
||||
str,
|
||||
)
|
||||
if fileFormat == 1
|
||||
else (
|
||||
(
|
||||
"结构延展方向:",
|
||||
lambda a: isin(
|
||||
a,
|
||||
{
|
||||
"z+": ["z+", "Z+"],
|
||||
"x+": ["X+", "x+"],
|
||||
"z-": ["Z-", "z-"],
|
||||
"x-": ["x-", "X-"],
|
||||
},
|
||||
),
|
||||
)
|
||||
if (playerFormat == 2 and fileFormat == 2)
|
||||
else ()
|
||||
)
|
||||
),
|
||||
(
|
||||
()
|
||||
if playerFormat == 1
|
||||
else (
|
||||
(
|
||||
"基础空白方块:",
|
||||
str,
|
||||
)
|
||||
if (playerFormat == 2 and fileFormat == 2)
|
||||
else (
|
||||
f"最大结构高度:",
|
||||
int,
|
||||
)
|
||||
)
|
||||
),
|
||||
]:
|
||||
if args:
|
||||
try:
|
||||
prompts.append(args[1](input(args[0])))
|
||||
except Exception:
|
||||
print(args)
|
||||
|
||||
|
||||
print(f"正在处理 {midi_path} :")
|
||||
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:
|
||||
cvt_method = to_addon_pack_in_score
|
||||
elif playerFormat == 0:
|
||||
cvt_method = to_addon_pack_in_delay
|
||||
elif playerFormat == 2:
|
||||
cvt_method = to_addon_pack_in_repeater
|
||||
elif fileFormat == 2:
|
||||
if playerFormat == 1:
|
||||
cvt_method = to_mcstructure_file_in_score
|
||||
elif playerFormat == 0:
|
||||
cvt_method = to_mcstructure_file_in_delay
|
||||
elif playerFormat == 2:
|
||||
cvt_method = to_mcstructure_file_in_repeater
|
||||
|
||||
# 测试
|
||||
|
||||
# print(cvt_mid)
|
||||
|
||||
|
||||
print(
|
||||
" 指令总长:{},最高延迟:{}".format(
|
||||
*(
|
||||
cvt_method(
|
||||
cvt_mid,
|
||||
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,
|
||||
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,
|
||||
out_path,
|
||||
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||
*prompts[3:],
|
||||
)
|
||||
)
|
||||
)
|
||||
if fileFormat == 1
|
||||
else (
|
||||
" 结构大小:{},延迟总数:{},指令数量:{}".format(
|
||||
*(
|
||||
cvt_method(
|
||||
cvt_mid,
|
||||
out_path,
|
||||
*prompts[3:],
|
||||
)
|
||||
)
|
||||
)
|
||||
if playerFormat == 1
|
||||
else " 结构大小:{},延迟总数:{}".format(
|
||||
*(
|
||||
cvt_method(
|
||||
cvt_mid,
|
||||
out_path,
|
||||
# Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
|
||||
*prompts[3:],
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
exitSth = input("回车退出").lower()
|
||||
if exitSth == "record":
|
||||
import json
|
||||
|
||||
with open("./demo_config.json", "w", encoding="utf-8") as f:
|
||||
json.dump(prompts, f)
|
||||
elif exitSth == "delrec":
|
||||
os.remove("./demo_config.json")
|
||||
@@ -1,14 +0,0 @@
|
||||
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,
|
||||
)
|
||||
)
|
||||
@@ -1,16 +0,0 @@
|
||||
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,
|
||||
)
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
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 +0,0 @@
|
||||
SEE: https://mingfengpigeon.mit-license.org/
|
||||
@@ -1,5 +0,0 @@
|
||||
__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
|
||||
@@ -1,142 +0,0 @@
|
||||
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',
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
"""
|
||||
版权所有 © 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),
|
||||
)
|
||||
@@ -1,39 +0,0 @@
|
||||
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} 个音符")
|
||||
@@ -1,33 +0,0 @@
|
||||
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,
|
||||
),
|
||||
)
|
||||
@@ -1,33 +0,0 @@
|
||||
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,
|
||||
),
|
||||
)
|
||||
@@ -1,34 +0,0 @@
|
||||
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)
|
||||
136
pyproject.toml
136
pyproject.toml
@@ -1,101 +1,49 @@
|
||||
[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",
|
||||
]
|
||||
[tool.briefcase]
|
||||
project_name = "Musicreater"
|
||||
bundle = "com.ryoun.musicreater"
|
||||
version = "0.0.1"
|
||||
url = "https://musicreater.ryoun.com/musicreater"
|
||||
license = "Apache Software License"
|
||||
author = 'Eilles Wan'
|
||||
author_email = "W-YI_DoctorYI@outlook.com"
|
||||
|
||||
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",
|
||||
]
|
||||
[tool.briefcase.app.musicreater]
|
||||
formal_name = "Musicreater"
|
||||
description = "Musicreater is an Eilles's app that is used for creating musics in Minecraft: Bedrock Edition"
|
||||
icon = "src/musicreater/resources/musicreater"
|
||||
sources = ['src/musicreater']
|
||||
requires = []
|
||||
|
||||
|
||||
[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"
|
||||
[tool.briefcase.app.musicreater.macOS]
|
||||
requires = [
|
||||
'toga-cocoa>=0.3.0.dev20',
|
||||
]
|
||||
|
||||
[tool.briefcase.app.musicreater.linux]
|
||||
requires = [
|
||||
'toga-gtk>=0.3.0.dev20',
|
||||
]
|
||||
system_requires = [
|
||||
'libgirepository1.0-dev',
|
||||
'libcairo2-dev',
|
||||
'libpango1.0-dev',
|
||||
'libwebkitgtk-3.0-0',
|
||||
'gir1.2-webkit-3.0',
|
||||
]
|
||||
|
||||
[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",
|
||||
]
|
||||
[tool.briefcase.app.musicreater.windows]
|
||||
requires = [
|
||||
'toga-winforms>=0.3.0.dev20',
|
||||
]
|
||||
|
||||
# Mobile deployments
|
||||
[tool.briefcase.app.musicreater.iOS]
|
||||
requires = [
|
||||
'toga-iOS>=0.3.0.dev20',
|
||||
]
|
||||
|
||||
[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"
|
||||
[tool.briefcase.app.musicreater.android]
|
||||
requires = [
|
||||
'toga-android>=0.3.0.dev20',
|
||||
]
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.6 MiB |
@@ -1,260 +0,0 @@
|
||||
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)
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
# 模拟两种写法
|
||||
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
|
||||
@@ -1,39 +0,0 @@
|
||||
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,42 +0,0 @@
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from scipy.optimize import curve_fit
|
||||
|
||||
|
||||
def q_function1(x, a, a2, c1,):
|
||||
return a * np.log( x + a2,)+ c1
|
||||
|
||||
def q_function2(x, b, b2, b3, b4, c2):
|
||||
return b * ((x + b2) ** b3) + b4 * (x+b2) + c2
|
||||
|
||||
|
||||
x_data = np.array([0, 16, 32, 48, 64, 80, 96, 112, 128])
|
||||
y_data = np.array([16, 10, 6.75, 4, 2.5, 1.6, 0.8, 0.3, 0])
|
||||
|
||||
|
||||
p_est1, err_est1 = curve_fit(q_function1, x_data[:5], y_data[:5], maxfev=1000000)
|
||||
p_est2, err_est2 = curve_fit(q_function2, x_data[4:], y_data[4:], maxfev=1000000)
|
||||
|
||||
|
||||
print(q_function1(x_data[:5], *p_est1))
|
||||
print(q_function2(x_data[4:], *p_est2))
|
||||
|
||||
print("参数一:",*p_est1)
|
||||
print("参数二:",*p_est2)
|
||||
|
||||
# 绘制图像
|
||||
plt.plot(
|
||||
np.arange(0, 64.1, 0.1), q_function1(np.arange(0, 64.1, 0.1), *p_est1), label=r"FIT1"
|
||||
)
|
||||
plt.plot(
|
||||
np.arange(64, 128.1, 0.1), q_function2(np.arange(64, 128.1, 0.1), *p_est2), label=r"FIT2"
|
||||
)
|
||||
|
||||
|
||||
plt.scatter(x_data, y_data, color="red") # 标记给定的点
|
||||
# plt.xlabel('x')
|
||||
# plt.ylabel('y')
|
||||
plt.title("Function Fit")
|
||||
plt.legend()
|
||||
# plt.grid(True)
|
||||
plt.show()
|
||||
@@ -1,37 +0,0 @@
|
||||
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
|
||||
# return -58.863374003875954 *((x+12.41481943150274 )**0.9973316187745871 ) +57.92341268595151 * (x+ 13.391132186222036) + -32.92986286030519
|
||||
return -8.081720684086314 * np.log( vol + 14.579508825070013,)+ 37.65806375944386
|
||||
|
||||
|
||||
def q_function2(vol):
|
||||
return 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649) + -6.313841334963396 * (vol + 2592.272889454798) + 4558.496367823575
|
||||
|
||||
# 生成 x 值
|
||||
x_values = np.linspace(0, 128, 1000)
|
||||
|
||||
|
||||
x_data = np.array([0,16,32,48,64,80,96,112,128])
|
||||
y_data = np.array([16, 10, 6.75, 4, 2.5, 1.6, 0.8, 0.3, 0])
|
||||
|
||||
|
||||
print(q_function1(x_data))
|
||||
print(q_function2(x_data))
|
||||
|
||||
# 绘制图像
|
||||
plt.plot(x_values, q_function1(x_values,),label = "fit1")
|
||||
plt.plot(x_values, q_function2(x_values,),label = "fit2")
|
||||
plt.scatter(x_data, y_data, color='red') # 标记给定的点
|
||||
# plt.scatter(x_data, y_data2, color='green') # 标记给定的点
|
||||
# plt.scatter(x_data, y_data3, color='blue') # 标记给定的点
|
||||
plt.xlabel('x')
|
||||
plt.ylabel('y')
|
||||
plt.title('Function')
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
plt.show()
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,14 +0,0 @@
|
||||
音乐谱面文件
|
||||
|
||||
测试片段.mid、测试片段.mscz
|
||||
|
||||
皆系 音·创 库所用之连带软件
|
||||
禁止在 音·创 库以外传播
|
||||
不允许独立发行、独立副本
|
||||
不允许未经允许的二次创作
|
||||
不允许脱离于 音·创 独立存在
|
||||
不得随任何其他非 音·创 软件连带发布
|
||||
不得用于商业用途
|
||||
若 音·创 库被用于商业用途,应当将其剔除
|
||||
|
||||
版权所有 © 2026 玉衡Alioth
|
||||
1401
src/Musicreater.py
Normal file
1401
src/Musicreater.py
Normal file
File diff suppressed because it is too large
Load Diff
1
src/musicreater.dist-info/INSTALLER
Normal file
1
src/musicreater.dist-info/INSTALLER
Normal file
@@ -0,0 +1 @@
|
||||
briefcase
|
||||
10
src/musicreater.dist-info/METADATA
Normal file
10
src/musicreater.dist-info/METADATA
Normal file
@@ -0,0 +1,10 @@
|
||||
Metadata-Version: 2.1
|
||||
Briefcase-Version: 0.3.5
|
||||
Name: musicreater
|
||||
Formal-Name: Musicreater
|
||||
App-ID: com.ryoun.musicreater.musicreater
|
||||
Version: 0.0.1
|
||||
Home-page: https://musicreater.ryoun.com/musicreater
|
||||
Author: Eilles Wan
|
||||
Author-email: W-YI_DoctorYI@outlook.com
|
||||
Summary: Musicreater is an Eilles's app that is used for creating musics in Minecraft: Bedrock Edition
|
||||
364
src/musicreater/Cmd_Msct.py
Normal file
364
src/musicreater/Cmd_Msct.py
Normal file
@@ -0,0 +1,364 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
# W-YI 金羿
|
||||
# QQ 2647547478
|
||||
# 音·创 开发交流群 861684859
|
||||
# Email EillesWan2006@163.com W-YI_DoctorYI@outlook.com
|
||||
# 版权所有 Team-Ryoun 金羿
|
||||
# 若需转载或借鉴 请附作者
|
||||
|
||||
|
||||
# 代码写的并非十分的漂亮,还请大佬多多包涵;本软件源代码依照Apache软件协议公开
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
import sys
|
||||
|
||||
from musicreater.msctspt.threadOpera import NewThread
|
||||
from musicreater.msctspt.bugReporter import version
|
||||
from musicreater.nmcsup.log import log
|
||||
|
||||
__version__ = version.version[1]+version.version[0]
|
||||
__author__ = 'W-YI (金羿)'
|
||||
|
||||
|
||||
log("系统工作————————加载变量及函数")
|
||||
|
||||
|
||||
print("更新执行位置...")
|
||||
|
||||
if sys.platform == 'win32':
|
||||
os.chdir(__file__[:len(__file__)-__file__[len(__file__)::-1].index('\\')])
|
||||
log("更新执行位置,当前文件位置"+__file__)
|
||||
else:
|
||||
try:
|
||||
os.chdir(__file__[:len(__file__) -
|
||||
__file__[len(__file__)::-1].index('/')])
|
||||
except:
|
||||
pass
|
||||
log("其他平台:"+sys.platform+"更新执行位置,当前文件位置"+__file__)
|
||||
print('完成!')
|
||||
|
||||
|
||||
|
||||
print('建立变量,存入内存,载入字典常量函数')
|
||||
|
||||
# 主体部分
|
||||
|
||||
# 支持多文件同时操作
|
||||
|
||||
# dataset[{ 'mainset':{ 'x':'y' }, 'musics': [ { 'set' :{ 'A':'B' } , 'note' : [ [ 'a' , b ], ] }, ] }, ]
|
||||
|
||||
# 编辑:
|
||||
# 修改主设置: dataset[第几个项目]['mainset']['什么设置'] = '设置啥'
|
||||
# 修改音乐: dataset[第几个项目]['musics'][第几个音轨]['notes'][第几个音符][音符还是时间(0,1)] = 改成啥
|
||||
# 修改音轨设置: dataset[第几个项目]['musics'][第几个音轨]['set']['什么设置'] = '设置啥'
|
||||
#
|
||||
# 新增音轨: dataset[第几个项目]['musics'].append(datasetmodelpart)
|
||||
#
|
||||
'''
|
||||
dataset=[
|
||||
{
|
||||
'mainset':{
|
||||
'PackName':"Ryoun",
|
||||
'MusicTitle':'Noname',
|
||||
'IsRepeat':False,
|
||||
'PlayerSelect':''
|
||||
},
|
||||
'musics':[
|
||||
{
|
||||
'set':{
|
||||
'EntityName':'music_support',
|
||||
'ScoreboardName':'music_support',
|
||||
'Instrument':'harp',
|
||||
'FileName':"Music"
|
||||
},
|
||||
'notes':[
|
||||
[0.0,1.0],
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
'''
|
||||
|
||||
global dataset
|
||||
|
||||
dataset = [
|
||||
{
|
||||
'mainset': {
|
||||
'PackName': "Ryoun",
|
||||
'MusicTitle': 'Noname',
|
||||
'IsRepeat': False,
|
||||
'PlayerSelect': ''
|
||||
},
|
||||
'musics': [
|
||||
{
|
||||
'set': {
|
||||
'EntityName': 'MusicSupport',
|
||||
'ScoreboardName': 'MusicSupport',
|
||||
'Instrument': 'note.harp',
|
||||
'FileName': "Music"
|
||||
},
|
||||
'notes': [
|
||||
[0.0, 1.0],
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
global is_new_file
|
||||
global is_save
|
||||
global ProjectName
|
||||
global NowMusic
|
||||
|
||||
is_new_file = True
|
||||
is_save = True
|
||||
ProjectName = ''
|
||||
NowMusic = 0
|
||||
|
||||
def DMM(): # 反回字典用于编辑
|
||||
datasetmodelpart = {
|
||||
'set': {
|
||||
'EntityName': 'MusicSupport',
|
||||
'ScoreboardName': 'MusicSupport',
|
||||
'Instrument': 'note.harp',
|
||||
'FileName': "Music"
|
||||
},
|
||||
'notes': []
|
||||
}
|
||||
return datasetmodelpart
|
||||
|
||||
print("完成")
|
||||
|
||||
# 菜单命令
|
||||
print('加载菜单命令...')
|
||||
|
||||
def exitapp(cmd):
|
||||
|
||||
log("程序正常退出", False)
|
||||
global is_save
|
||||
if is_save == False:
|
||||
if '/s' in cmd:
|
||||
saveProject()
|
||||
else:
|
||||
print("您尚未保存,请使用 /s 开关保存并退出")
|
||||
return False
|
||||
|
||||
try:
|
||||
global dataset
|
||||
del dataset
|
||||
except:
|
||||
pass
|
||||
|
||||
if '/c' in cmd:
|
||||
print("清除log(此句不载入日志)")
|
||||
try:
|
||||
if os.path.exists("./log/"):
|
||||
shutil.rmtree("./log/")
|
||||
if os.path.exists("./logs/"):
|
||||
shutil.rmtree("./logs/")
|
||||
if os.path.exists("./cache/"):
|
||||
shutil.rmtree("./cache/")
|
||||
except:
|
||||
print("无法清除日志及临时文件")
|
||||
|
||||
exit()
|
||||
|
||||
print('退出函数加载完成!')
|
||||
|
||||
print("载入文件读取函数")
|
||||
|
||||
def ReadFile(fn: str):
|
||||
from nmcsup.nmcreader import ReadFile as fileRead
|
||||
k = fileRead(fn)
|
||||
if k == False:
|
||||
log("找不到"+fn)
|
||||
return False
|
||||
else:
|
||||
return k
|
||||
|
||||
def ReadMidi(midfile: str):
|
||||
from nmcsup.nmcreader import ReadMidi as midiRead
|
||||
k = midiRead(midfile)
|
||||
if k == False:
|
||||
log("找不到"+midfile)
|
||||
return False
|
||||
else:
|
||||
return k
|
||||
|
||||
print('完成!')
|
||||
|
||||
print("载入命令函数")
|
||||
|
||||
def saveProject(cmd: list):
|
||||
global is_new_file
|
||||
if '/a' in cmd:
|
||||
log("另存项目")
|
||||
ProjectName = cmd[cmd.index('/a')+1]
|
||||
else:
|
||||
if is_new_file:
|
||||
print("初次存储请使用 /a 开关规定存储文件名")
|
||||
log("文件未保存")
|
||||
return False
|
||||
|
||||
log("存储文件:"+ProjectName)
|
||||
with open(ProjectName, 'w', encoding='utf-8') as f:
|
||||
json.dump(dataset[0], f)
|
||||
global is_save
|
||||
is_save = True
|
||||
|
||||
print('保存项目函数加载完成!')
|
||||
|
||||
def loadMusic(cmd: list):
|
||||
if '/mid' in cmd:
|
||||
th = NewThread(ReadMidi, (cmd[cmd.index('/mid')+1],))
|
||||
th.start()
|
||||
|
||||
def midiSPT(th):
|
||||
for i in th.getResult():
|
||||
datas = DMM()
|
||||
datas['notes'] = i
|
||||
dataset[0]['musics'].append(datas)
|
||||
del th
|
||||
global is_save
|
||||
is_save = False
|
||||
threading.Thread(target=midiSPT, args=(th,)).start()
|
||||
del th
|
||||
elif '/txt' in cmd:
|
||||
th = NewThread(ReadFile, (cmd[cmd.index('/txt')+1],))
|
||||
th.start()
|
||||
|
||||
def midiSPT(th):
|
||||
for i in th.getResult():
|
||||
datas = DMM()
|
||||
datas['notes'] = i
|
||||
dataset[0]['musics'].append(datas)
|
||||
del th
|
||||
global is_save
|
||||
is_save = False
|
||||
threading.Thread(target=midiSPT, args=(th,)).start()
|
||||
elif '/input' in cmd:
|
||||
datas = []
|
||||
for i in cmd[cmd.index('/input')+1:]:
|
||||
datas.append([str(i), 1.0])
|
||||
from nmcsup.trans import note2list
|
||||
datat = DMM()
|
||||
datat['notes'] = note2list(datas)
|
||||
dataset[0]['musics'].append(datat)
|
||||
del datas, datat
|
||||
global is_save
|
||||
is_save = False
|
||||
else:
|
||||
log("无参数,无法读入。")
|
||||
print("请查看帮助文件查看指令格式。")
|
||||
return False
|
||||
|
||||
print('音轨载入函数加载完成!')
|
||||
|
||||
def funBuild(cmd: list):
|
||||
if '/file' in cmd:
|
||||
from msctspt.funcOpera import makeFuncFiles
|
||||
makepath = cmd[cmd.index('/file')+1]
|
||||
if makepath[-1] != '/':
|
||||
makepath += '/'
|
||||
makeFuncFiles(dataset[0], makepath)
|
||||
elif '/directory' in cmd:
|
||||
from msctspt.funcOpera import makeFunDir
|
||||
makepath = cmd[cmd.index('/directory')+1]
|
||||
if makepath[-1] != '/':
|
||||
makepath += '/'
|
||||
makeFunDir(dataset[0], makepath)
|
||||
elif '/mcpack' in cmd:
|
||||
import zipfile
|
||||
from msctspt.funcOpera import makeFunDir
|
||||
makepath = cmd[cmd.index('/mcpack')+1]
|
||||
if makepath[-1] != '/':
|
||||
makepath += '/'
|
||||
|
||||
if not os.path.exists('./temp/'):
|
||||
os.makedirs('./temp/')
|
||||
makeFunDir(dataset[0], './temp/')
|
||||
shutil.move('./temp/'+dataset[0]['mainset']['PackName'] +
|
||||
"Pack/behavior_packs/"+dataset[0]['mainset']['PackName']+"/functions", './')
|
||||
shutil.move('./temp/'+dataset[0]['mainset']['PackName'] + "Pack/behavior_packs/" +
|
||||
dataset[0]['mainset']['PackName']+"/manifest.json", './')
|
||||
with zipfile.ZipFile(makepath+dataset[0]['mainset']['PackName']+'.mcpack', "w") as zipobj:
|
||||
for i in os.listdir('./functions/'):
|
||||
zipobj.write('./functions/'+i)
|
||||
zipobj.write('./manifest.json')
|
||||
shutil.move('./functions', './temp/')
|
||||
shutil.move('./manifest.json', './temp/')
|
||||
shutil.rmtree("./temp/")
|
||||
else:
|
||||
log("无参数,无法读入。")
|
||||
print("请查看帮助文件查看指令格式。")
|
||||
return False
|
||||
|
||||
print("函数建立函数加载完成")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def __main__():
|
||||
|
||||
if sys.platform == 'win32':
|
||||
os.system("cls")
|
||||
else:
|
||||
os.system("clear")
|
||||
|
||||
if sys.platform in ('win32', 'linux'):
|
||||
print("您当前的运行环境为标准桌面,您可以打开 Musicreater.py 运行窗口模式的 音·创")
|
||||
print("您也可以输入 win 指令在不退出命令行模式的同时打开窗口模式\n")
|
||||
|
||||
print(__author__+" 音·创 —— 当前核心版本 "+__version__+'\n')
|
||||
|
||||
nowWorkPath = os.path.split(os.path.realpath(__file__))[0]
|
||||
|
||||
while True:
|
||||
|
||||
strcmd = input("MSCT "+nowWorkPath+">")
|
||||
cmd = strcmd.lower().split(' ')
|
||||
|
||||
if cmd[0] == 'exit':
|
||||
exitapp(cmd[1:])
|
||||
elif cmd[0] == 'save':
|
||||
saveProject(cmd[1:])
|
||||
elif cmd[0] == 'load':
|
||||
loadMusic(cmd[1:])
|
||||
elif cmd[0] == 'win':
|
||||
def run(cmd):
|
||||
os.system(cmd)
|
||||
if sys.platform == 'win32':
|
||||
NewThread(run, ("python "+os.path.split(os.path.realpath(__file__))
|
||||
[0]+"/Musicreater.py",)).start()
|
||||
else:
|
||||
NewThread(run, ("python3 "+os.path.split(os.path.realpath(__file__))
|
||||
[0]+"/Musicreater.py",)).start()
|
||||
elif cmd[0] == 'chdir':
|
||||
nowWorkPath = os.path.realpath(cmd[1])
|
||||
os.chdir(nowWorkPath)
|
||||
elif cmd[0] == 'build':
|
||||
funBuild(cmd[1:])
|
||||
else:
|
||||
os.system(strcmd)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
__main__
|
||||
4
src/musicreater/__main__.py
Normal file
4
src/musicreater/__main__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from musicreater.app import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main().main_loop()
|
||||
133
src/musicreater/app.py
Normal file
133
src/musicreater/app.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
音·创(Musicreater)是由金羿(W-YI)开发的一款《我的世界》基岩版音乐生成辅助软件
|
||||
"""
|
||||
|
||||
|
||||
# W-YI 金羿
|
||||
# QQ 2647547478
|
||||
# 音·创 开发交流群 861684859
|
||||
# Email EillesWan2006@163.com W-YI_DoctorYI@outlook.com
|
||||
# 版权所有 Team-Ryoun 金羿
|
||||
# 若需转载或借鉴 请附作者
|
||||
|
||||
|
||||
# 代码写的并非十分的漂亮,还请大佬多多包涵;本软件源代码依照Apache软件协议公开
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
import toga
|
||||
from toga.style import Pack
|
||||
from toga.style.pack import COLUMN, ROW
|
||||
|
||||
from musicreater.Cmd_Msct import *
|
||||
from musicreater.msctspt.bugReporter import version
|
||||
|
||||
from musicreater.resources.ChineseLang import LANGUAGE
|
||||
|
||||
|
||||
|
||||
__version__ = version.version[1]+version.version[0]
|
||||
__author__ = 'W-YI (金羿)'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if sys.platform == 'win32':
|
||||
os.chdir(__file__[:len(__file__)-__file__[len(__file__)::-1].index('\\')])
|
||||
log("更新执行位置,当前文件位置"+__file__)
|
||||
else:
|
||||
try:
|
||||
os.chdir(__file__[:len(__file__) -
|
||||
__file__[len(__file__)::-1].index('/')])
|
||||
except:
|
||||
pass
|
||||
log("其他平台:"+sys.platform+"更新执行位置,当前文件位置"+__file__)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Musicreater(toga.App):
|
||||
'''音·创 本体\n
|
||||
W-YI 金羿\n
|
||||
QQ 2647547478\n
|
||||
音·创 开发交流群 861684859\n
|
||||
Email EillesWan2006@163.com W-YI_DoctorYI@outlook.com\n
|
||||
版权所有 Team-Ryoun 金羿\n
|
||||
若需转载或借鉴 请附作者\n
|
||||
'''
|
||||
|
||||
|
||||
|
||||
def startup(self):
|
||||
|
||||
|
||||
# Start to draw the window
|
||||
|
||||
main_box = toga.Box(style=Pack(direction=COLUMN))
|
||||
|
||||
self.noticeLabel = toga.Label('MSCT >>>',style=Pack(padding=(0, 5)))
|
||||
|
||||
self.inputBox = toga.TextInput(style=Pack(flex=1))
|
||||
#dispImage = toga.ImageView("./resources/oddevenmatrix.png")
|
||||
|
||||
cmd_box = toga.Box(style=Pack(direction=ROW, padding=5))
|
||||
|
||||
cmd_box.add(self.noticeLabel)
|
||||
cmd_box.add(self.inputBox)
|
||||
# cmd_box.add(dispImage)
|
||||
|
||||
button = toga.Button(
|
||||
LANGUAGE['main']['run'],
|
||||
on_press=self.showMessage,
|
||||
style=Pack(padding=5)
|
||||
)
|
||||
|
||||
|
||||
main_box.add(cmd_box)
|
||||
main_box.add(button)
|
||||
|
||||
|
||||
self.main_window = toga.MainWindow(title=self.formal_name)
|
||||
self.main_window.content = main_box
|
||||
self.main_window.show()
|
||||
|
||||
self.main_window.info_dialog('',"{} {} —— {} {}".format(__author__,LANGUAGE['main']['name'],LANGUAGE['main']['version'],__version__))
|
||||
|
||||
self.nowWorkPath = os.path.split(os.path.realpath(__file__))[0]
|
||||
|
||||
|
||||
def showMessage(self, widget):
|
||||
|
||||
strcmd = self.inputBox.value
|
||||
|
||||
cmd = strcmd.lower().split(' ')
|
||||
|
||||
if cmd[0] == 'exit':
|
||||
if exitapp(cmd[1:]) == False:
|
||||
self.main_window.info_dialog('',LANGUAGE['command']['FormatError'])
|
||||
elif cmd[0] == 'save':
|
||||
if saveProject(cmd[1:]) == False:
|
||||
self.main_window.info_dialog('',LANGUAGE['command']['FormatError'])
|
||||
elif cmd[0] == 'load':
|
||||
if loadMusic(cmd[1:]) == False:
|
||||
self.main_window.info_dialog('',LANGUAGE['command']['FormatError'])
|
||||
elif cmd[0] == 'chdir':
|
||||
self.main_window.info_dialog('',LANGUAGE['command']['NotAvailable'])
|
||||
return
|
||||
nowWorkPath = os.path.realpath(cmd[1])
|
||||
os.chdir(nowWorkPath)
|
||||
elif cmd[0] == 'build':
|
||||
if funBuild(cmd[1:]) == False:
|
||||
self.main_window.info_dialog('',LANGUAGE['command']['FormatError'])
|
||||
else:
|
||||
return
|
||||
os.system(strcmd)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
return Musicreater()
|
||||
141
src/musicreater/fcwslib/__init__.py
Normal file
141
src/musicreater/fcwslib/__init__.py
Normal file
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '0.0.1'
|
||||
__all__ = ['run_server', 'subscribe', 'unsubscribe', 'send_command', 'tellraw']
|
||||
__author__ = 'Fuckcraft <https://gitee.com/fuckcraft>'
|
||||
|
||||
import os
|
||||
import json
|
||||
import uuid
|
||||
import logging
|
||||
import asyncio
|
||||
import time
|
||||
import websockets
|
||||
|
||||
# 写这段代码的时候,只有我和上帝知道这段代码是干什么的。
|
||||
# 现在只有上帝知道。
|
||||
|
||||
# 此函数用于向 Minecraft 订阅请求
|
||||
async def subscribe(websocket, event_name):
|
||||
'''
|
||||
参数:
|
||||
: websocket : websocket 对象 :
|
||||
: event_name : 需要订阅的请求 :
|
||||
|
||||
返回:
|
||||
None
|
||||
'''
|
||||
|
||||
response = {
|
||||
'body': {
|
||||
'eventName': str(event_name) # 示例:PlayerMessage
|
||||
},
|
||||
'header': {
|
||||
'requestId': str(uuid.uuid4()),
|
||||
'messagePurpose': 'subscribe',
|
||||
'version': 1,
|
||||
'messageType': 'commandRequest'
|
||||
}
|
||||
}
|
||||
|
||||
# 增加 json 的可读性
|
||||
# response = json.dumps(response, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
|
||||
response = json.dumps(response)
|
||||
|
||||
await websocket.send(response)
|
||||
|
||||
# 此函数用于向 Minecraft 消除订阅请求
|
||||
async def unsubscribe(webscket):
|
||||
'''
|
||||
参数:
|
||||
: websocket : websocket 对象 :
|
||||
: event_name : 需要消除订阅的请求 :
|
||||
|
||||
返回:
|
||||
None
|
||||
'''
|
||||
|
||||
response = {
|
||||
"body": {
|
||||
"eventName": str(event_name) # PlayerMessage
|
||||
},
|
||||
"header": {
|
||||
"requestId": str(uuid.uuid4()),
|
||||
"messagePurpose": "unsubscribe",
|
||||
"version": 1,
|
||||
"messageType": "commandRequest"
|
||||
}
|
||||
}
|
||||
|
||||
# 增加 json 的可读性
|
||||
# response = json.dumps(response, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
|
||||
response = json.dumps(response)
|
||||
|
||||
await websocket.send(response)
|
||||
|
||||
# 此函数用于向 Minecraft 执行命令
|
||||
async def send_command(websocket, command):
|
||||
'''
|
||||
参数:
|
||||
: websocket : websocket 对象 :
|
||||
: command : 执行的命令 :
|
||||
|
||||
返回:
|
||||
None
|
||||
'''
|
||||
|
||||
response = {
|
||||
'body': {
|
||||
'origin': {
|
||||
'type': 'player'
|
||||
},
|
||||
'commandLine': str(command),
|
||||
'version': 1
|
||||
},
|
||||
'header': {
|
||||
'requestId': str(uuid.uuid4()),
|
||||
'messagePurpose': 'commandRequest',
|
||||
'version': 1,
|
||||
'messageType': 'commandRequest'
|
||||
}
|
||||
}
|
||||
|
||||
# 增加 json 的可读性
|
||||
# response = json.dumps(response, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
|
||||
response = json.dumps(response)
|
||||
|
||||
await websocket.send(response)
|
||||
|
||||
# 此函数用于向 Minecraft 发送消息
|
||||
async def tellraw(websocket, message):
|
||||
'''
|
||||
参数:
|
||||
: websocket : websocket 对象 :
|
||||
: message : 发送的消息 :
|
||||
|
||||
返回:
|
||||
None
|
||||
'''
|
||||
|
||||
command = {
|
||||
'rawtext':[
|
||||
{
|
||||
'text':'[{}] {}'.format(time.asctime(), message)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# 增加 json 可读性
|
||||
# command = json.dumps(command, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
|
||||
command = json.dumps(command)
|
||||
command = 'tellraw @a {}'.format(command)
|
||||
|
||||
await send_command(websocket, command)
|
||||
|
||||
def run_server(function):
|
||||
# 修改 ip 地址和端口
|
||||
start_server = websockets.serve(function, 'localhost', 8080)
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
asyncio.get_event_loop().run_forever()
|
||||
|
||||
2
src/musicreater/log/2021-12-31 00_13_19.msct.log
Normal file
2
src/musicreater/log/2021-12-31 00_13_19.msct.log
Normal file
@@ -0,0 +1,2 @@
|
||||
00:13:19 更新执行位置,当前文件位置F:\W-YI\Programming\音·创\程序\src\musicreater\Cmd_Msct.py
|
||||
00:13:19 更新执行位置,当前文件位置F:\W-YI\Programming\音·创\程序\src\musicreater\app.py
|
||||
173
src/musicreater/msctspt/MSCT Dev Log.txt
Normal file
173
src/musicreater/msctspt/MSCT Dev Log.txt
Normal file
@@ -0,0 +1,173 @@
|
||||
音·创(Musicreater)是由金羿(W-YI)开发的一款《我的世界》基岩版音乐生成辅助软件
|
||||
本软件源代码依照Apache软件协议公开。
|
||||
|
||||
Copyright © W-YI 2021
|
||||
|
||||
本软件是金羿前作函数音创和世界音创的集合版本,同时增加了大量功能更新。
|
||||
|
||||
|
||||
To-Do
|
||||
1.可以导出自定义的结构文件用于存储要导入地图中的结构
|
||||
2.进度条
|
||||
3.可以将音乐写入音符盒(红乐)
|
||||
4.更换tk库为briefcase库,支持安卓系统
|
||||
5.支持自动给音符盒绑定更多的音色
|
||||
6.可以由.schematic文件导入地图,亦可反向处理
|
||||
7.支持自定义指令方块区域的长宽高等
|
||||
8.支持自定义创建websockeet服务器播放音乐(感谢由 Fuckcraft <https://github.com/fuckcraft> “鸣凤鸽子”等 带来的我的世界websocket服务器功能)
|
||||
9.支持使用红石播放指令音乐
|
||||
10.支持采用延时的播放器
|
||||
11.支持使用bdx导出结构
|
||||
12.支持采用tp的方法播放
|
||||
13.支持识别曲谱图片解析音乐
|
||||
14.支持使用瀑布流的方式播放音乐
|
||||
15.帮助菜单
|
||||
16.多语言
|
||||
17.支持自动搜寻地图目录位置(网易&微软)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
新更新日志
|
||||
|
||||
Beta 0.0.4.3
|
||||
2021 11 3~2021 12 26
|
||||
1.不断改进包以及代码可读性
|
||||
2.修正部分源码错误
|
||||
3.修正部分格式错误
|
||||
4.加强对Linux系统的支持
|
||||
5.新增命令行模式
|
||||
6.代码中新增大量注释
|
||||
|
||||
|
||||
Beta 0.0.4 ~ Beta 0.0.4.2
|
||||
2021 11 20 ~ 2021 11 21
|
||||
1.完全支持Linux系统
|
||||
2.支持以.RyStruct导出结构
|
||||
3.修复大量bug
|
||||
4.支持拖拽打开(参数1为.msct文件)
|
||||
|
||||
|
||||
Beta 0.0.3.1~0.0.3.5
|
||||
2021 11 1~2021 11 2
|
||||
1.更新部分提示信息使之更加科学
|
||||
2.强制性限制不得使用非Win32平台打开此程序
|
||||
3.支持在Windwos7上使用此程序(发现错误并解决:DLL缺失MSVCP140.dll)
|
||||
4.开始对结构导出进行部分支持
|
||||
5.发现红乐写入的错误,正在排查修复
|
||||
|
||||
|
||||
|
||||
|
||||
Beta 0.0.3
|
||||
2021 10 29 ~ 2021 10 31
|
||||
1.修改部分窗口排版
|
||||
2.修复指令载入地图的结构的错误
|
||||
3.修复指令生成出现的指令错误(感谢 昀梦<QQ1515399885> 找出bug并指正)
|
||||
4.支持生成红石音乐(以音符盒存储的音乐),并写入地图
|
||||
5.修复了生成指令音乐导致的错误
|
||||
6.修复bdx文件y轴过长导致无法生成完毕的错误,现在bdx的y轴为200格
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Beta 0.0.2
|
||||
2021 10 25
|
||||
1.修复了邮件发送错误报告无法生成压缩包的问题
|
||||
2.修复了导入音轨时无法获得进程返回值的问题
|
||||
3.修复了.bdx文件生成时无法选择文件的问题
|
||||
4.修复了生成指令音乐(计分板)没有起始方块的问题
|
||||
5.新增了创建Websocket的功能,可以在localhost:8080创建websocket服务器播放音乐(感谢由 Fuckcraft <https://gitee.com/fuckcraft> “鸣凤鸽子”等 带来的我的世界websocket服务器功能(fcwslib) )
|
||||
6.解决了打包成可执行文件时无法正常退出的问题
|
||||
|
||||
|
||||
|
||||
Beta 0.0.1
|
||||
2021 10月
|
||||
1.支持生成.bdx文件(感谢由 Charlie_Ping “查理平” 带来的bdx转换功能)
|
||||
2.逐步增强对安卓系统的支持
|
||||
3.逐步放弃对Windows的强行要求
|
||||
4.逐步提升性能,增加多线程
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Alpha部分更新日志
|
||||
|
||||
Alpha 0.0.0
|
||||
2021 8 20
|
||||
1.集合了 函数音创0.1.4.1 与 世界音创Beta0.0.1 的功能于本应用
|
||||
2.新增了可以生成 .mcpack 包的方法
|
||||
|
||||
Alpha 0.0.1
|
||||
2021 8 25
|
||||
1.新增两个彩蛋(就是函数音创命令行模式的彩蛋
|
||||
|
||||
Alpha 0.0.1.1
|
||||
2021 8 25
|
||||
1.修复大量已知问题
|
||||
2.修复了部分彩蛋bug,但是程序仍然不会正常退出
|
||||
3.菜单界面优化
|
||||
|
||||
Alpha 0.0.1.2
|
||||
2021.8.29
|
||||
1.修复大量已知问题
|
||||
2.现在可以操作指令文件了
|
||||
3.窗口界面优化
|
||||
|
||||
Alpha 0.0.2
|
||||
2021 9 5
|
||||
1.修复部分已知问题
|
||||
2.指令链导入之时仅生成链式方块且允许折转
|
||||
3.[Dev]正在逐步支持结构导出
|
||||
|
||||
Alpha 0.0.3
|
||||
2021 9 7
|
||||
1.修复指令链转入世界的摆放错误
|
||||
2.指令存储的音乐(包括函数)支持不同玩家不同的播放
|
||||
3.支持播放进度条
|
||||
4.删除彩蛋任务栏图标
|
||||
4.[Dev]已确定导出结构格式
|
||||
|
||||
Alpha 0.0.3.1
|
||||
2021 9 11
|
||||
1.取消输入玩家选择器时不会出现bug了
|
||||
2.删除日志文件修改为删除临时文件
|
||||
3.可以删除用于确认档案存在的文件了
|
||||
|
||||
Alpha 0.0.4
|
||||
2021 10 4-5
|
||||
1.可以将大函数导入世界(以一条链执行多个函数的方式)
|
||||
2.关闭了试听音乐的功能,但是保留其函数于funOpera.py中
|
||||
3.修改部分代码,减少更多bug
|
||||
4.发现指令链转入世界的摆放错误,但是没改正
|
||||
|
||||
1.0.3
|
||||
2021 10 5-6
|
||||
1.解决一些已知问题
|
||||
2.解决了文件读取造成的字符编码问题
|
||||
3.使用PyPinyin库将汉字转化为拼音首字母
|
||||
|
||||
Alpha 0.0.4.1
|
||||
2021 10 9
|
||||
1.将清除日志功能设置为结束后统一清除,避免了清除过程中文件占用导致的问题
|
||||
|
||||
Alpha 0.0.5
|
||||
2021 10 10
|
||||
1.支持使用邮件方式发送错误报告(日志)
|
||||
|
||||
Alpha 0.0.5.1
|
||||
1.修复了邮件发送错误报告无法发送的问题
|
||||
2.修复了打包成.exe文件之后无法正常退出的问题
|
||||
|
||||
|
||||
|
||||
|
||||
0
src/musicreater/msctspt/__init__.py
Normal file
0
src/musicreater/msctspt/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user