1 Commits

Author SHA1 Message Date
EillesWan
acf4c6907e 直接更换库为BeeWare作为窗口库 2021-12-31 07:54:56 +08:00
93 changed files with 4821 additions and 6116 deletions

3
.gitattributes vendored
View File

@@ -1,3 +0,0 @@
*.yaml linguist-language=Python
*.xml linguist-language=Python
*.md linguist-language=Python

188
.gitignore vendored
View File

@@ -1,30 +1,38 @@
# sth. can't open
/msctPkgver/secrets/*.py
/msctPkgver/secrets/*.c
# mystuff
/.vscode
*.mid
*.midi
*.mcpack
*.bdx
*.json
*.mcstructure
/logs
/languages
/llc_cli.py
/utils
test.py
RES.txt
# 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/
@@ -36,129 +44,23 @@ lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# 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/

View File

@@ -1,219 +1,201 @@
**注意,以下条款或版权声明应当且必须是高于此项目中任何其他声明的**
1. 音·创的全部开发者享有其完整版权,其开发者可以在任一时刻终止以后音·创源代码开放,若经由其开发者授予特殊权利,则授权对象可以将源代码进行特定的被特殊授权的操作
2. 音·创或(及)其代码允许在 Apache2.0 协议的条款与说明下进行非商业使用
3. 除部分代码特殊声明外,音·创允许对其或(及)其代码进行商业化使用,但是需要经过音·创主要开发者(诸葛亮与八卦阵、金羿)的一致授权,同时,授权对象在商业化授权的使用过程中必须依照 Apache2.0 协议的条款与说明
4. 若存在对于音·创包含的部分代码的特殊开源声明,则此部分代码依照其特定的开源方式授权,但若此部分代码经由此部分代码的主要开发者一致特殊授权后商用,则授权对象在商用时依照此部分的开发者所准许的方式(或条款)进行商用
5. Apache2.0 协议的英文原文副本可见下文
> The English Translation of the TERMS AND CONDITIONS above is listed below
>
> This translated version is for reference only and has no legal effect.
>
> The version with legal effect is the Chinese version above.
**Note, The TERMS AND CONDITIONS below should and must be above all others in this project**
1. *Musicreater* is fully copyrighted by all its developers, the developers have the right to make *Musicreater* close sourced at any time. Operations are permitted under specific terms instructed by its developer(s).
2. Non-commercial use of *Musicreater* and(or) its source code is permitted under Apache License 2.0.
3. Commercial use of *Musicreater* is permitted under Apache License 2.0 with the unanimous permission of the steering developers of *Musicreater* (*bgArray*诸葛亮与八卦阵 and *Eilles*金羿).
4. *Musicreater* is open sourced under priority given:
1. License granted by the core developer(s) of a section after negotiation.
2. Explicitly stated license.
3. Apache 2.0 License.
5. A copy of the original Apache Lisence 2.0 can be found below.
```text
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
Copyright 2022 TriM-Organization 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & all the developers of Musicreater
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.
```
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.

View File

@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
"""一个简单的我的世界音频转换库
音·创 (Musicreater)
是一款免费开源的针对《我的世界》的midi音乐转换库
Musicreater(音·创)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__version__ = "1.4.0"
__vername__ = "红石指令音乐的生成"
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), ("鸣凤鸽子", "MingFengPigeon"))
__all__ = [
# 主要类
"MidiConvert",
# 附加类
# "SingleNote",
"SingleCommand",
# "TimeStamp", 未来功能
]
from .main import *

View File

@@ -1,295 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Tuple
"""
存放常量与数值性内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
x = "x"
"""
x
"""
y = "y"
"""
y
"""
z = "z"
"""
z
"""
DEFAULT_PROGRESSBAR_STYLE = (
r"%%N [ %%s/%^s %%% __________ %%t|%^t ]",
("§e=§r", "§7=§r"),
)
"""
默认的进度条样式组
"""
# 以下是由 Touch “偷吃” 带来的高准确率音效对照表
# 包括乐音乐器对照和打击乐器对照
PITCHED_INSTRUMENT_TABLE: Dict[int, Tuple[str, int]] = {
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.guitar", 7),
7: ("note.harp", 6),
8: ("note.bell", 4), # 打击乐器无音域
9: ("note.bell", 4),
10: ("note.iron_xylophone", 6),
11: ("note.iron_xylophone", 6),
12: ("note.iron_xylophone", 6),
13: ("note.xylophone", 4),
14: ("note.chime", 4),
15: ("note.harp", 6),
16: ("note.bass", 8),
17: ("note.harp", 6),
18: ("note.flute", 5),
19: ("note.harp", 6),
20: ("note.harp", 6),
21: ("note.flute", 5),
22: ("note.flute", 5),
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.flute", 5),
41: ("note.flute", 5),
42: ("note.flute", 5),
43: ("note.flute", 5),
44: ("note.iron_xylophone", 6),
45: ("note.guitar", 7),
46: ("note.harp", 6),
47: ("note.bd", 7),
48: ("note.guitar", 7),
49: ("note.guitar", 7),
50: ("note.bit", 6),
51: ("note.bit", 6),
52: ("note.flute", 5),
53: ("note.flute", 5),
54: ("note.flute", 5),
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.bit", 6),
70: ("note.banjo", 6),
71: ("note.flute", 5),
72: ("note.flute", 5),
73: ("note.flute", 5),
74: ("note.flute", 5),
75: ("note.flute", 5),
76: ("note.harp", 6),
77: ("note.harp", 6),
78: ("note.flute", 5),
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.bell", 4),
109: ("note.flute", 5),
110: ("note.flute", 5),
111: ("note.guitar", 7),
112: ("note.bell", 4),
113: ("note.bell", 4),
114: ("note.flute", 5),
115: ("note.cow_bell", 5),
116: ("note.bd", 7), # 打击乐器无音域
117: ("note.bass", 8),
118: ("note.bit", 6),
119: ("firework.blast", 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: ("firework.twinkle", 7), # 打击乐器无音域
127: ("note.snare", 7), # 打击乐器无音域
}
PERCUSSION_INSTRUMENT_TABLE: Dict[int, Tuple[str, int]] = {
34: ("note.bd", 7),
35: ("note.bd", 7),
36: ("note.hat", 7),
37: ("note.snare", 7),
38: ("note.snare", 7),
39: ("fire.ignite", 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),
}
PERCUSSION_INSTRUMENT_LIST: List[str] = [
"note.snare",
"note.bd",
"note.hat",
"note.basedrum",
"firework.blast",
"firework.twinkle",
"fire.ignite",
]
INSTRUMENT_BLOCKS_TABLE: Dict[str, Tuple[str]] = {
"note.bass": ("planks",),
"note.snare": ("sand",),
"note.hat": ("glass",),
"note.bd": ("stone",),
"note.basedrum": ("stone",),
"note.bell": ("gold_block",),
"note.flute": ("clay",),
"note.chime": ("packed_ice",),
"note.guitar": ("wool",),
"note.xylobone": ("bone_block",),
"note.iron_xylophone": ("iron_block",),
"note.cow_bell": ("soul_sand",),
"note.didgeridoo": ("pumpkin",),
"note.bit": ("emerald_block",),
"note.banjo": ("hay_block",),
"note.pling": ("glowstone",),
"note.bassattack": ("command_block",), # 无法找到此音效
"note.harp": ("dirt",),
# 呃……
"firework.blast": ("sandstone",),
"firework.twinkle": ("red_sandstone",),
"fire.ignite": ("concrete_powder",),
}
# 即将启用
height2note = {
0.5: 0,
0.53: 1,
0.56: 2,
0.6: 3,
0.63: 4,
0.67: 5,
0.7: 6,
0.75: 7,
0.8: 8,
0.84: 9,
0.9: 10,
0.94: 11,
1.0: 12,
1.05: 13,
1.12: 14,
1.2: 15,
1.25: 16,
1.33: 17,
1.4: 18,
1.5: 19,
1.6: 20,
1.7: 21,
1.8: 22,
1.9: 23,
2.0: 24,
}
"""音高对照表\n
MC音高:音符盒音调"""

View File

@@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放一堆报错类型
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
class MSCTBaseException(Exception):
"""音·创库版本的所有错误均继承于此"""
def __init__(self, *args):
"""音·创库版本的所有错误均继承于此"""
super().__init__(*args)
def miao(
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(RuntimeError):
"""指令格式与目标格式不匹配而引起的错误"""
def __init__(self, *args):
"""指令格式与目标格式不匹配而引起的错误"""
super().__init__("指令格式不匹配", *args)
class CrossNoteError(MidiFormatException):
"""同通道下同音符交叉出现所产生的错误"""
def __init__(self, *args):
"""同通道下同音符交叉出现所产生的错误"""
super().__init__("同通道下同音符交叉", *args)
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 ZeroSpeedError(MidiFormatException):
"""以0作为播放速度的错误"""
def __init__(self, *args):
"""以0作为播放速度的错误"""
super().__init__("播放速度为0", *args)

View File

@@ -1,623 +0,0 @@
# -*- coding: utf-8 -*-
"""
新版本功能以及即将启用的函数
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import random
from typing import Dict, List, Tuple, Union
from .constants import INSTRUMENT_BLOCKS_TABLE
from .exceptions import *
from .main import MidiConvert
from .subclass import *
from .utils import *
class FutureMidiConvertRSNB(MidiConvert):
"""
加入红石音乐适配
"""
music_command_list: Dict[int, SingleNoteBox]
"""音乐指令列表"""
@staticmethod
def soundID_to_block(sound_id: str, random_select: bool = False) -> str:
"""
将我的世界乐器名改作音符盒所需的对应方块名称
Parameters
----------
sound_id: str
将我的世界乐器名
random_select: bool
是否随机选取对应方块
Returns
-------
str方块名称
"""
try:
if random_select:
return random.choice(INSTRUMENT_BLOCKS_TABLE[sound_id])
else:
return INSTRUMENT_BLOCKS_TABLE[sound_id][0]
except KeyError:
return "air"
def to_note_list_in_delay(
self,
) -> Tuple[Dict[int, SingleNoteBox], int]:
"""
使用金羿的转换思路将midi转换为我的世界音符盒音高、乐器及延迟列表并输出每个音符之后的延迟
Returns
-------
tuple( Dict[int, SingleNoteBox], int音乐时长游戏刻 )
"""
self.to_music_channels()
tracks = {}
note_range = {}
InstID = -1
# cmd_amount = 0
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
for track_no, track in self.channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
# block_id = self.soundID_to_block((
# self.perc_inst_to_soundID_withX(msg[1])
# if SpecialBits
# else self.inst_to_souldID_withX(InstID)
# )[0])
# delaytime_now = round(msg[-1] / 50)
note_ = 2 ** ((msg[1] - 60) / 12)
try:
tracks[msg[-1]].append(
(
InstID,
note_,
)
)
except KeyError:
tracks[msg[-1]] = [(InstID, note_)]
try:
note_range[InstID]["max"] = max(
note_range[InstID]["max"], note_
)
note_range[InstID]["min"] = min(
note_range[InstID]["min"], note_
)
except KeyError:
note_range[InstID] = {"max": note_, "min": note_}
del InstID
all_ticks = list(tracks.keys())
all_ticks.sort()
results = []
print(note_range)
exit()
for i in range(len(all_ticks)):
for j in range(len(tracks[all_ticks[i]])):
results.append(
SingleCommand(
tracks[all_ticks[i]][j],
tick_delay=(
0
if j != 0
else (
all_ticks[i] - all_ticks[i - 1]
if i != 0
else all_ticks[i]
)
),
annotation="{}播放{}%{}".format(
mctick2timestr(i), max_volume * 100, ""
),
)
)
self.music_command_list = results
self.music_tick_num = max(all_ticks)
return results, self.music_tick_num
class FutureMidiConvertM4(MidiConvert):
"""
加入插值算法优化音感
: 经测试,生成效果已经达到,感觉良好
"""
# 临时用的插值计算函数
@staticmethod
def _linear_note(
_note: SingleNote,
_apply_time_division: int = 100,
) -> List[Tuple[int, int, int, int, float],]:
"""传入音符数据,返回以半秒为分割的插值列表
:param _note: SingleNote 音符
:return list[tuple(int开始时间毫秒, int乐器, int音符, int力度内置, float音量播放),]"""
totalCount = int(_note.duration / _apply_time_division)
if totalCount == 0:
return [
(_note.start_time, _note.inst, _note.pitch, _note.velocity, 1),
]
# print(totalCount)
result: List[Tuple[int, int, int, int, float],] = []
for _i in range(totalCount):
result.append(
(
_note.start_time + _i * _apply_time_division,
_note.instrument,
_note.pitch,
_note.velocity,
((totalCount - _i) / totalCount),
)
)
return result
# 简单的单音填充
def to_command_list_in_score(
self,
scoreboard_name: str = "mscplay",
max_volume: float = 1.0,
speed: float = 1.0,
) -> Tuple[List[List[SingleCommand]], int, int]:
"""
使用金羿的转换思路使用完全填充算法优化音感后将midi转换为我的世界命令列表
Parameters
----------
scoreboard_name: str
我的世界的计分板名称
max_volume: float
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
Returns
-------
tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
self.to_music_channels()
note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[])
InstID = -1
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# nowChannel = []
for track_no, track in self.channels[i].items():
noteMsgs = []
MsgIndex = []
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
noteMsgs.append(msg[1:])
MsgIndex.append(msg[1])
elif msg[0] == "NoteE":
if msg[1] in MsgIndex:
note_channels[i].append(
SingleNote(
InstID,
msg[1],
noteMsgs[MsgIndex.index(msg[1])][1],
noteMsgs[MsgIndex.index(msg[1])][2],
msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2],
track_number=track_no,
)
)
noteMsgs.pop(MsgIndex.index(msg[1]))
MsgIndex.pop(MsgIndex.index(msg[1]))
del InstID
tracks = []
cmd_amount = 0
max_score = 0
# 此处 我们把通道视为音轨
for no, track in note_channels.items():
# 如果当前通道为空 则跳过
if not track:
continue
SpecialBits = True if no == 9 else False
track_now = []
for note in track:
for every_note in self._linear_note(
note, 50 if note.track_no == 0 else 500
):
soundID, _X = (
self.perc_inst_to_soundID_withX(note.pitch)
if SpecialBits
else self.inst_to_souldID_withX(note.inst)
)
score_now = round(every_note[0] / speed / 50)
max_score = max(max_score, score_now)
mc_pitch = 2 ** ((note.pitch - 60 - _X) / 12)
blockmeter = (
1
/ (1 if note.track_no == 0 else 0.9)
/ max_volume
/ every_note[4]
- 1
)
track_now.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores=({}={})]".format(scoreboard_name, score_now)
.replace("(", r"{")
.replace(")", r"}")
)
+ "playsound {} @s ^ ^ ^{} {} {}".format(
soundID,
blockmeter,
note.velocity / 128,
"" if SpecialBits else mc_pitch,
),
annotation="{}播放{}%({}BM)的{}".format(
mctick2timestr(score_now),
max_volume * 100,
blockmeter,
"{}:{}".format(soundID, note.pitch),
),
),
)
cmd_amount += 1
if track_now:
self.music_command_list.extend(track_now)
tracks.append(track_now)
self.music_tick_num = max_score
return (tracks, cmd_amount, max_score)
# 简单的单音填充的延迟应用
def to_command_list_in_delay(
self,
max_volume: float = 1.0,
speed: float = 1.0,
player_selector: str = "@a",
) -> Tuple[List[SingleCommand], int]:
"""
使用金羿的转换思路使用完全填充算法优化音感后将midi转换为我的世界命令列表并输出每个音符之后的延迟
Parameters
----------
max_volume: float
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
player_selector: str
玩家选择器,默认为`@a`
Returns
-------
tuple( list[SingleCommand,...], int音乐时长游戏刻 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
self.to_music_channels()
note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[])
InstID = -1
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# nowChannel = []
for track_no, track in self.channels[i].items():
noteMsgs = []
MsgIndex = []
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
noteMsgs.append(msg[1:])
MsgIndex.append(msg[1])
elif msg[0] == "NoteE":
if msg[1] in MsgIndex:
note_channels[i].append(
SingleNote(
InstID,
msg[1],
noteMsgs[MsgIndex.index(msg[1])][1],
noteMsgs[MsgIndex.index(msg[1])][2],
msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2],
track_number=track_no,
)
)
noteMsgs.pop(MsgIndex.index(msg[1]))
MsgIndex.pop(MsgIndex.index(msg[1]))
del InstID
tracks = {}
InstID = -1
# open("RES.TXT", "w", encoding="utf-8").write(str(note_channels))
# 此处 我们把通道视为音轨
for no, track in note_channels.items():
# 如果当前通道为空 则跳过
if not track:
continue
SpecialBits = True if no == 9 else False
for note in track:
for every_note in self._linear_note(
note, 50 if note.track_no == 0 else 500
):
soundID, _X = (
self.perc_inst_to_soundID_withX(note.pitch)
if SpecialBits
else self.inst_to_souldID_withX(note.inst)
)
score_now = round(every_note[0] / speed / 50)
try:
tracks[score_now].append(
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} "
+ (
""
if SpecialBits
else f"{2 ** ((note.pitch - 60 - _X) / 12)}"
)
)
except KeyError:
tracks[score_now] = [
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} "
+ (
""
if SpecialBits
else f"{2 ** ((note.pitch - 60 - _X) / 12)}"
)
]
all_ticks = list(tracks.keys())
all_ticks.sort()
results = []
for i in range(len(all_ticks)):
for j in range(len(tracks[all_ticks[i]])):
results.append(
SingleCommand(
tracks[all_ticks[i]][j],
tick_delay=(
0
if j != 0
else (
all_ticks[i] - all_ticks[i - 1]
if i != 0
else all_ticks[i]
)
),
annotation="{}播放{}%{}".format(
mctick2timestr(i), max_volume * 100, ""
),
)
)
self.music_command_list = results
self.music_tick_num = max(all_ticks)
return results, self.music_tick_num
class FutureMidiConvertM5(MidiConvert):
"""
加入同刻偏移算法优化音感
"""
# 神奇的偏移音
def to_command_list_in_delay(
self,
max_volume: float = 1.0,
speed: float = 1.0,
player_selector: str = "@a",
) -> Tuple[List[SingleCommand], int]:
"""
使用金羿的转换思路使用同刻偏移算法优化音感后将midi转换为我的世界命令列表并输出每个音符之后的延迟
Parameters
----------
max_volume: float
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
player_selector: str
玩家选择器,默认为`@a`
Returns
-------
tuple( list[SingleCommand,...], int音乐时长游戏刻 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
self.to_music_channels()
tracks = {}
InstID = -1
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
# nowChannel = []
for track_no, track in self.channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
self.perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else self.inst_to_souldID_withX(InstID)
)
score_now = round(msg[-1] / float(speed) / 50)
# print(score_now)
try:
tracks[score_now].append(
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
+ (
""
if SpecialBits
else f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
)
except KeyError:
tracks[score_now] = [
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
+ (
""
if SpecialBits
else f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
]
all_ticks = list(tracks.keys())
all_ticks.sort()
results = []
for i in range(len(all_ticks)):
for j in range(len(tracks[all_ticks[i]])):
results.append(
SingleCommand(
tracks[all_ticks[i]][j],
tick_delay=(
(
0
if (
(all_ticks[i + 1] - all_ticks[i])
/ len(tracks[all_ticks[i]])
< 1
)
else 1
)
if j != 0
else (
(
all_ticks[i]
- all_ticks[i - 1]
- (
0
if (
(all_ticks[i] - all_ticks[i - 1])
/ len(tracks[all_ticks[i - 1]])
< 1
)
else (len(tracks[all_ticks[i - 1]]) - 1)
)
)
if i != 0
else all_ticks[i]
)
),
annotation="{}播放{}%{}".format(
mctick2timestr(
i + 0
if (
(all_ticks[i + 1] - all_ticks[i])
/ len(tracks[all_ticks[i]])
< 1
)
else j
),
max_volume * 100,
"",
),
)
)
self.music_command_list = results
self.music_tick_num = max(all_ticks)
return results, self.music_tick_num
class FutureMidiConvertM6(MidiConvert):
"""
加入插值算法优化音感,但仅用于第一音轨
"""
# TODO 没写完的!!!!

View File

@@ -1,225 +0,0 @@
# -*- coding: utf-8 -*-
"""
功能测试 若非已知 请勿更改
此文件仅供功能测试,并非实际调用的文件
请注意,此处的文件均为测试使用
不要更改 不要更改 不要更改
请注意这里的一切均需要其原作者更改
这里用于放置一些新奇的点子
用于测试
不要更改 不要更改 不要更改!
"""
# 音·创 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 版权所有 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
"""
音·创 (Musicreater)
是一款免费开源的针对《我的世界》的midi音乐转换库
Musicreater (音·创)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 ../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 Structure, Block
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]

View File

@@ -1,813 +0,0 @@
# -*- coding: utf-8 -*-
"""
音·创 (Musicreater)
是一款免费开源的针对《我的世界》的midi音乐转换库
Musicreater (音·创)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 音·创 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
# BUG退散BUG退散 BUG退散BUG退散
# 异常、错误作乱之时 異常、誤りが、困った時は
# 二六字组!万国码合!二六字组!万国码合! グループ!コード#!グループ!コード#!
# 赶快呼叫 程序员Let's Go 直ぐに呼びましょプログラマ レッツゴー!
import math
import os
from typing import List, Literal, Tuple, Union
import mido
from .constants import *
from .exceptions import *
from .subclass import *
from .utils import *
VoidMido = Union[mido.MidiFile, None] # void mido
"""
空Midi类类型
"""
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],]],],]
"""
"""
学习笔记:
tempo: microseconds per quarter note 毫秒每四分音符,换句话说就是一拍占多少毫秒
tick: midi帧
ticks_per_beat: 帧每拍,即一拍多少帧
那么:
tick / ticks_per_beat => amount_of_beats 拍数(四分音符数)
tempo * amount_of_beats => 毫秒数
所以:
tempo * tick / ticks_per_beat => 毫秒数
###########
seconds per tick:
(tempo / 1000000.0) / ticks_per_beat
seconds:
tick * tempo / 1000000.0 / ticks_per_beat
microseconds:
tick * tempo / 1000.0 / ticks_per_beat
gameticks:
tick * tempo / 1000000.0 / ticks_per_beat * 一秒多少游戏刻
"""
@dataclass(init=False)
class MidiConvert:
"""
将Midi文件转换为我的世界内容
"""
midi: VoidMido
"""MidiFile对象"""
midi_music_name: str
"""Midi乐曲名"""
enable_old_exe_format: bool
"""是否启用旧版execute指令格式"""
execute_cmd_head: str
"""execute指令头部"""
channels: ChannelType
"""频道信息字典"""
music_command_list: List[SingleCommand]
"""音乐指令列表"""
music_tick_num: int
"""音乐总延迟"""
progress_bar_command: List[SingleCommand]
"""进度条指令列表"""
def __init__(
self,
midi_obj: VoidMido,
midi_name: str,
enable_old_exe_format: bool = False,
):
"""
简单的midi转换类将midi对象转换为我的世界结构或者包
Parameters
----------
midi_obj: mido.MidiFile 对象
需要处理的midi对象
midi_name: MIDI乐曲名称
此音乐之名
enable_old_exe_format: bool
是否启用旧版(≤1.19)指令格式,默认为否
"""
self.midi: VoidMido = midi_obj
self.midi_music_name: str = midi_name
self.enable_old_exe_format: bool = enable_old_exe_format
self.execute_cmd_head = (
"execute {} ~ ~ ~ "
if enable_old_exe_format
else "execute as {} at @s positioned ~ ~ ~ run "
)
self.progress_bar_command = self.music_command_list = []
self.channels = {}
self.music_tick_num = 0
@classmethod
def from_midi_file(
cls,
midi_file_path: str,
old_exe_format: bool = False,
):
"""
直接输入文件地址将midi文件读入
Parameters
----------
midi_file: str
midi文件地址
enable_old_exe_format: bool
是否启用旧版(≤1.19)指令格式,默认为否
"""
midi_music_name = os.path.splitext(os.path.basename(midi_file_path))[0].replace(
" ", "_"
)
"""文件名,不含路径且不含后缀"""
try:
return cls(
mido.MidiFile(midi_file_path, clip=True),
midi_music_name,
old_exe_format,
)
except (ValueError, TypeError) as E:
raise MidiDestroyedError(f"文件{midi_file_path}损坏:{E}")
except FileNotFoundError as E:
raise FileNotFoundError(f"文件{midi_file_path}不存在:{E}")
# ……真的那么重要吗
# 我又几曾何时,知道祂真的会抛下我
@staticmethod
def inst_to_souldID_withX(
instrumentID: int,
) -> Tuple[str, int]:
"""
返回midi的乐器ID对应的我的世界乐器名对于音域转换算法如下
2**( ( msg.note - 60 - X ) / 12 ) 即为MC的音高其中
X的取值随乐器不同而变化
竖琴harp、电钢琴pling、班卓琴banjo、方波bit、颤音琴iron_xylophone 的时候为6
吉他的时候为7
贝斯bass、迪吉里杜管didgeridoo的时候为8
长笛flute、牛铃cou_bell的时候为5
钟琴bell、管钟chime、木琴xylophone的时候为4
而存在一些打击乐器bd(basedrum)、hat、snare没有音域则没有X那么我们返回7即可
Parameters
----------
instrumentID: int
midi的乐器ID
Returns
-------
tuple(str我的世界乐器名, int转换算法中的X)
"""
try:
return PITCHED_INSTRUMENT_TABLE[instrumentID]
except KeyError:
return "note.flute", 5
@staticmethod
def perc_inst_to_soundID_withX(instrumentID: int) -> Tuple[str, int]:
"""
对于Midi第10通道所对应的打击乐器返回我的世界乐器名
Parameters
----------
instrumentID: int
midi的乐器ID
Returns
-------
tuple(str我的世界乐器名, int转换算法中的X)
"""
try:
return PERCUSSION_INSTRUMENT_TABLE[instrumentID]
except KeyError:
return "note.bd", 7
# 明明已经走了
# 凭什么还要在我心里留下缠绵缱绻
def form_progress_bar(
self,
max_score: int,
scoreboard_name: str,
progressbar_style: tuple = DEFAULT_PROGRESSBAR_STYLE,
) -> List[SingleCommand]:
"""
生成进度条
Parameters
----------
maxscore: int
midi的乐器ID
scoreboard_name: str
所使用的计分板名称
progressbar_style: tuple
此参数详见 ../docs/库的生成与功能文档.md#进度条自定义
Returns
-------
list[SingleCommand,]
"""
pgs_style = progressbar_style[0]
"""用于被替换的进度条原始样式"""
"""
| 标识符 | 指定的可变量 |
|---------|----------------|
| `%%N` | 乐曲名(即传入的文件名)|
| `%%s` | 当前计分板值 |
| `%^s` | 计分板最大值 |
| `%%t` | 当前播放时间 |
| `%^t` | 曲目总时长 |
| `%%%` | 当前进度比率 |
| `_` | 用以表示进度条占位|
"""
perEach = max_score / pgs_style.count("_")
"""每个进度条代表的分值"""
result: List[SingleCommand] = []
if r"%^s" in pgs_style:
pgs_style = pgs_style.replace(r"%^s", str(max_score))
if r"%^t" in pgs_style:
pgs_style = pgs_style.replace(r"%^t", mctick2timestr(max_score))
sbn_pc = scoreboard_name[:2]
if r"%%%" in pgs_style:
result.append(
SingleCommand(
'scoreboard objectives add {}PercT dummy "百分比计算"'.format(sbn_pc),
annotation="新增临时计算用计分板(百分比)",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players set MaxScore {} {}".format(
scoreboard_name, max_score
),
annotation="设定此音乐最大计分",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players set n100 {} 100".format(scoreboard_name),
annotation="设置常量100",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players operation @s {} = @s {}".format(
sbn_pc + "PercT", scoreboard_name
),
annotation="为临时变量赋值",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players operation @s {} *= n100 {}".format(
sbn_pc + "PercT", scoreboard_name
),
annotation="改变临时变量的单位为百分比(扩大精度)",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players operation @s {} /= MaxScore {}".format(
sbn_pc + "PercT", scoreboard_name
),
annotation="使用临时变量计算百分比",
)
)
# 那是假的
# 一切都并未留下痕迹啊
# 那梦又是多么的真实……
if r"%%t" in pgs_style:
result.append(
SingleCommand(
'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format(sbn_pc),
annotation="新增临时计算计分板(分)",
)
)
result.append(
SingleCommand(
'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(sbn_pc),
annotation="新增临时计算计分板(秒)",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players set n20 {} 20".format(scoreboard_name),
annotation="设置常量20",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players set n60 {} 60".format(scoreboard_name),
annotation="设置常量60",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players operation @s {} = @s {}".format(
sbn_pc + "TMinT", scoreboard_name
),
annotation="为临时变量(分)赋值",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players operation @s {} /= n20 {}".format(
sbn_pc + "TMinT", scoreboard_name
),
annotation="将临时变量转换单位为秒(缩减精度)",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players operation @s {} = @s {}".format(
sbn_pc + "TSecT", sbn_pc + "TMinT"
),
annotation="为临时变量(秒)赋值",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players operation @s {} /= n60 {}".format(
sbn_pc + "TMinT", scoreboard_name
),
annotation="将临时变量(分)转换单位为分(缩减精度)",
)
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores={" + scoreboard_name + "=1..}]"
)
+ "scoreboard players operation @s {} %= n60 {}".format(
sbn_pc + "TSecT", scoreboard_name
),
annotation="将临时变量(秒)确定下来(框定精度区间)",
)
)
for i in range(pgs_style.count("_")):
npg_stl = (
pgs_style.replace("_", progressbar_style[1][0], i + 1)
.replace("_", progressbar_style[1][1])
.replace(r"%%N", self.midi_music_name)
if r"%%N" in pgs_style
else pgs_style.replace("_", progressbar_style[1][0], i + 1).replace(
"_", progressbar_style[1][1]
)
)
if r"%%s" in npg_stl:
npg_stl = npg_stl.replace(
r"%%s",
'"},{"score":{"name":"*","objective":"'
+ scoreboard_name
+ '"}},{"text":"',
)
if r"%%%" in npg_stl:
npg_stl = npg_stl.replace(
r"%%%",
r'"},{"score":{"name":"*","objective":"'
+ sbn_pc
+ r'PercT"}},{"text":"%',
)
if r"%%t" in npg_stl:
npg_stl = npg_stl.replace(
r"%%t",
r'"},{"score":{"name":"*","objective":"{-}TMinT"}},{"text":":"},'
r'{"score":{"name":"*","objective":"{-}TSecT"}},{"text":"'.replace(
r"{-}", sbn_pc
),
)
result.append(
SingleCommand(
self.execute_cmd_head.format(
r"@a[scores={"
+ scoreboard_name
+ f"={int(i * perEach)}..{math.ceil((i + 1) * perEach)}"
+ r"}]"
)
+ r'titleraw @s actionbar {"rawtext":[{"text":"'
+ npg_stl
+ r'"}]}',
annotation="进度条显示",
)
)
if r"%%%" in pgs_style:
result.append(
SingleCommand(
"scoreboard objectives remove {}PercT".format(sbn_pc),
annotation="移除临时计算计分板(百分比)",
)
)
if r"%%t" in pgs_style:
result.append(
SingleCommand(
"scoreboard objectives remove {}TMinT".format(sbn_pc),
annotation="移除临时计算计分板(分)",
)
)
result.append(
SingleCommand(
"scoreboard objectives remove {}TSecT".format(sbn_pc),
annotation="移除临时计算计分板(秒)",
)
)
self.progress_bar_command = result
return result
def to_music_channels(
self,
) -> ChannelType:
"""
使用金羿的转换思路将midi解析并转换为频道信息字典
Returns
-------
以频道作为分割的Midi信息字典:
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
"""
if self.midi is None:
raise MidiUnboundError(
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象这是不可复用的。"
)
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
midi_channels: ChannelType = empty_midi_channels()
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
# 我们来用通道统计音乐信息
# 但是是用分轨的思路的
for track_no, track in enumerate(self.midi.tracks):
microseconds = 0
if not track:
continue
for msg in track:
if msg.time != 0:
microseconds += msg.time * tempo / self.midi.ticks_per_beat / 1000
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
else:
try:
if not track_no in midi_channels[msg.channel].keys():
midi_channels[msg.channel][track_no] = []
except AttributeError as E:
print(msg, E)
if msg.type == "program_change":
midi_channels[msg.channel][track_no].append(
("PgmC", msg.program, microseconds)
)
elif msg.type == "note_on" and msg.velocity != 0:
midi_channels[msg.channel][track_no].append(
("NoteS", msg.note, msg.velocity, microseconds)
)
elif (msg.type == "note_on" and msg.velocity == 0) or (
msg.type == "note_off"
):
midi_channels[msg.channel][track_no].append(
("NoteE", msg.note, microseconds)
)
"""整合后的音乐通道格式
每个通道包括若干消息元素其中逃不过这三种:
1 切换乐器消息
("PgmC", 切换后的乐器ID: int, 距离演奏开始的毫秒)
2 音符开始消息
("NoteS", 开始的音符ID, 力度(响度), 距离演奏开始的毫秒)
3 音符结束消息
("NoteE", 结束的音符ID, 距离演奏开始的毫秒)"""
del tempo, self.channels
self.channels = midi_channels
# [print([print(no,tno,sum([True if i[0] == 'NoteS' else False for i in track])) for tno,track in cna.items()]) if cna else False for no,cna in midi_channels.items()]
return midi_channels
def to_command_list_in_score(
self,
scoreboard_name: str = "mscplay",
max_volume: float = 1.0,
speed: float = 1.0,
) -> Tuple[List[List[SingleCommand]], int, int]:
"""
使用金羿的转换思路将midi转换为我的世界命令列表
Parameters
----------
scoreboard_name: str
我的世界的计分板名称
max_volume: float
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
Returns
-------
tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
tracks = []
cmdAmount = 0
maxScore = 0
InstID = -1
self.to_music_channels()
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
for track_no, track in self.channels[i].items():
nowTrack = []
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
self.perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else self.inst_to_souldID_withX(InstID)
)
score_now = round(msg[-1] / float(speed) / 50)
maxScore = max(maxScore, score_now)
mc_pitch = "" if SpecialBits else 2 ** ((msg[1] - 60 - _X) / 12)
nowTrack.append(
SingleCommand(
self.execute_cmd_head.format(
"@a[scores=({}={})]".format(
scoreboard_name, score_now
)
.replace("(", r"{")
.replace(")", r"}")
)
+ "playsound {} @s ^ ^ ^{} {} {}".format(
soundID, 1 / max_volume - 1, msg[2] / 128, mc_pitch
),
annotation="{}播放{}%{}".format(
mctick2timestr(score_now),
max_volume * 100,
"{}:{}".format(soundID, mc_pitch),
),
),
)
cmdAmount += 1
if nowTrack:
self.music_command_list.extend(nowTrack)
tracks.append(nowTrack)
# print(cmdAmount)
del InstID
self.music_tick_num = maxScore
return (tracks, cmdAmount, maxScore)
def to_command_list_in_delay(
self,
max_volume: float = 1.0,
speed: float = 1.0,
player_selector: str = "@a",
) -> Tuple[List[SingleCommand], int, int]:
"""
使用金羿的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟
Parameters
----------
max_volume: float
最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
player_selector: str
玩家选择器,默认为`@a`
Returns
-------
tuple( list[SingleCommand,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume)
self.to_music_channels()
tracks = {}
InstID = -1
# cmd_amount = 0
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
# nowChannel = []
for track_no, track in self.channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
self.perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else self.inst_to_souldID_withX(InstID)
)
delaytime_now = round(msg[-1] / float(speed) / 50)
try:
tracks[delaytime_now].append(
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
+ (
""
if SpecialBits
else f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
)
except KeyError:
tracks[delaytime_now] = [
self.execute_cmd_head.format(player_selector)
+ f"playsound {soundID} @s ^ ^ ^{128 / max_volume / msg[2] - 1} {msg[2] / 128} "
+ (
""
if SpecialBits
else f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
]
# cmd_amount += 1
# print(cmd_amount)
del InstID
all_ticks = list(tracks.keys())
all_ticks.sort()
results = []
max_multi = 0
for i in range(len(all_ticks)):
max_multi = max(max_multi, len(tracks[all_ticks[i]]))
for j in range(len(tracks[all_ticks[i]])):
results.append(
SingleCommand(
tracks[all_ticks[i]][j],
tick_delay=(
0
if j != 0
else (
all_ticks[i] - all_ticks[i - 1]
if i != 0
else all_ticks[i]
)
),
annotation="{}播放{}%{}".format(
mctick2timestr(i), max_volume * 100, ""
),
)
)
self.music_command_list = results
self.music_tick_num = max(all_ticks)
return results, self.music_tick_num, max_multi
def copy_important(self):
dst = MidiConvert(
midi_obj=None,
midi_name=self.midi_music_name,
enable_old_exe_format=self.enable_old_exe_format,
)
dst.music_command_list = [i.copy() for i in self.music_command_list]
dst.progress_bar_command = [i.copy() for i in self.progress_bar_command]
dst.music_tick_num = self.music_tick_num
return dst

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放非音·创本体的附加内容(插件?)
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"ConvertConfig",
]
__author__ = (("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"))
from .main import *

View File

@@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放关于文件打包的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import os
import uuid
import zipfile
import datetime
from typing import List, Union, Literal
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
"""使用compression指定的算法打包目录为zip文件\n
默认算法为DEFLATED(8),可用算法如下:\n
STORED = 0\n
DEFLATED = 8\n
BZIP2 = 12\n
LZMA = 14\n
"""
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(
pack_description: str = "",
pack_version: Union[List[int], Literal[None]] = None,
pack_name: str = "",
pack_uuid: Union[str, Literal[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,
]
return {
"format_version": 1,
"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,
}
],
}

View File

@@ -1,215 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关BDX结构操作的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import List
from ..constants import x, y, z
from ..subclass import SingleCommand
from .common import bottem_side_length_of_smallest_square_bottom_box
bdx_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_key[axis][0 if value == -1 else 1]
pointer = sum(
[
1 if i else 0
for i in (
value != -1,
value < -1 or value > 1,
value < -128 or value > 127,
value < -32768 or value > 32767,
)
]
)
return bdx_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,
):
"""
使用指定项目返回指定的指令方块放置指令项
:param command: `str`
指令
:param particularValue:
方块特殊值,即朝向
:0 下 无条件
:1 上 无条件
:2 z轴负方向 无条件
:3 z轴正方向 无条件
:4 x轴负方向 无条件
:5 x轴正方向 无条件
:6 下 无条件
:7 下 无条件
:8 下 有条件
:9 上 有条件
:10 z轴负方向 有条件
:11 z轴正方向 有条件
:12 x轴负方向 有条件
:13 x轴正方向 有条件
:14 下 有条件
:14 下 有条件
注意此处特殊值中的条件会被下面condition参数覆写
:param impluse: `int 0|1|2`
方块类型
0脉冲 1循环 2连锁
:param condition: `bool`
是否有条件
:param needRedstone: `bool`
是否需要红石
:param tickDelay: `int`
执行延时
:param customName: `str`
悬浮字
lastOutput: `str`
上次输出字符串,注意此处需要留空
:param executeOnFirstTick: `bool`
首刻执行(循环指令方块是否激活后立即执行若为False则从激活时起延迟后第一次执行)
:param trackOutput: `bool`
是否输出
:return:str
"""
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[SingleCommand],
max_height: int = 64,
):
"""
:param commands: 指令列表(指令, 延迟)
:param max_height: 生成结构最大高度
:return 成功与否,成功返回(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_key[x][1]
now_x += 1
else:
_bytes += bdx_key[z][int(z_forward)]
else:
_bytes += bdx_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],
)

View File

@@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以生成BDX结构文件的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_BDX_file_in_score",
"to_BDX_file_in_delay"
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -1,220 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import os
import brotli
from ...main import MidiConvert
from ...subclass import SingleCommand
from ..bdx import (bdx_move, commands_to_BDX_bytes,
form_command_block_in_BDX_bytes, x, y, z)
from ..main import ConvertConfig
def to_BDX_file_in_score(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
author: str = "Eilles",
max_height: int = 64,
):
"""
将midi以计分播放器形式转换为BDX结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
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, data_cfg.volume_ratio, data_cfg.speed_multiplier
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.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
+ (
[
SingleCommand(
command="scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(max_score + 20)
+ "}] "
+ scoreboard_name,
annotation="自动重置计分板",
)
]
if auto_reset
else []
),
max_height - 1,
)
if data_cfg.progressbar_style:
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
midi_cvt.form_progress_bar(
max_score, scoreboard_name, data_cfg.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(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.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,
data_cfg: ConvertConfig,
player: str = "@a",
author: str = "Eilles",
max_height: int = 64,
):
"""
使用method指定的转换算法将midi转换为BDX结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
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(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.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 data_cfg.progressbar_style:
scb_name = midi_cvt.midi_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, data_cfg.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(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.bdx")
),
"ab+",
) as f:
f.write(brotli.compress(_bytes + b"XE"))
return len(cmdlist), max_delay, size, finalPos

View File

@@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放通用的普遍性的插件内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import math
def bottem_side_length_of_smallest_square_bottom_box(total: int, maxHeight: int):
"""给定总方块数量和最大高度,返回所构成的图形外切正方形的边长
:param total: 总方块数量
:param maxHeight: 最大高度
:return: 外切正方形的边长 int"""
return math.ceil(math.sqrt(math.ceil(total / maxHeight)))

View File

@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以生成函数附加包的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_function_addon_in_score"
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -1,132 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import json
import os
import shutil
from typing import Tuple
from ...main import MidiConvert
from ..archive import behavior_mcpack_manifest, compress_zipfile
from ..main import ConvertConfig
def to_function_addon_in_score(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
) -> Tuple[int, int]:
"""
将midi以计分播放器形式转换为我的世界函数附加包
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
是否自动重置计分板
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_score(
scoreboard_name, data_cfg.volume_ratio, data_cfg.speed_multiplier
)
# 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目然后创建
if os.path.exists(f"{data_cfg.dist_path}/temp/functions/"):
shutil.rmtree(f"{data_cfg.dist_path}/temp/functions/")
os.makedirs(f"{data_cfg.dist_path}/temp/functions/mscplay")
# 写入manifest.json
with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包MCFUNCTION(MCPACK) 计分播放器 - 由 音·创 生成",
pack_name=midi_cvt.midi_music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
),
fp=f,
indent=4,
)
# 将命令列表写入文件
index_file = open(
f"{data_cfg.dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
for i in range(len(cmdlist)):
index_file.write(f"function mscplay/track{i + 1}\n")
with open(
f"{data_cfg.dist_path}/temp/functions/mscplay/track{i + 1}.mcfunction",
"w",
encoding="utf-8",
) as f:
f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]]))
index_file.writelines(
(
"scoreboard players add @a[scores={"
+ scoreboard_name
+ "=1..}] "
+ scoreboard_name
+ " 1\n",
(
"scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(maxscore + 20)
+ "..}]"
+ f" {scoreboard_name}\n"
)
if auto_reset
else "",
f"function mscplay/progressShow\n" if data_cfg.progressbar_style else "",
)
)
if data_cfg.progressbar_style:
with open(
f"{data_cfg.dist_path}/temp/functions/mscplay/progressShow.mcfunction",
"w",
encoding="utf-8",
) as f:
f.writelines(
"\n".join(
[
single_cmd.cmd
for single_cmd in midi_cvt.form_progress_bar(
maxscore, scoreboard_name, data_cfg.progressbar_style
)
]
)
)
index_file.close()
if os.path.exists(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack"):
os.remove(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack")
compress_zipfile(
f"{data_cfg.dist_path}/temp/",
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
)
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
return maxlen, maxscore

View File

@@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放附加内容功能
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from dataclasses import dataclass
from typing import Tuple, Union, Literal
from ..constants import DEFAULT_PROGRESSBAR_STYLE
@dataclass(init=False)
class ConvertConfig:
"""
转换通用设置存储类
"""
volume_ratio: float
"""音量比例"""
speed_multiplier: float
"""速度倍率"""
progressbar_style: Union[Tuple[str, Tuple[str, str]], Literal[None]]
"""进度条样式组"""
dist_path: str
"""输出目录"""
def __init__(
self,
output_path: str,
volume: float = 1.0,
speed: float = 1.0,
progressbar: Union[bool, Tuple[str, Tuple[str, str]]] = True,
):
"""
将已经转换好的数据内容指令载入MC可读格式
Parameters
----------
output_path: str
生成内容的输出目录
volume: float
音量比率,范围为(0,1],其原理为在距离玩家 (1 / volume -1) 的地方播放音频
speed: float
速度倍率,注意:这里的速度指的是播放速度倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
progressbar: bool|tuple[str, Tuple[str,]]
进度条,当此参数为 `True` 时使用默认进度条,为其他的**值为真**的参数时识别为进度条自定义参数,为其他**值为假**的时候不生成进度条
"""
self.dist_path = output_path
"""输出目录"""
self.volume_ratio = volume
"""音量比例"""
self.speed_multiplier = speed
"""速度倍率"""
if progressbar:
# 此处是对于仅有 True 的参数和自定义参数的判断
# 改这一段没🐎
if progressbar is True:
self.progressbar_style = DEFAULT_PROGRESSBAR_STYLE
"""进度条样式组"""
else:
self.progressbar_style = progressbar
"""进度条样式组"""
else:
self.progressbar_style = None
"""进度条样式组"""

View File

@@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以生成单个mcstructure文件的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_mcstructure_file_in_delay",
"to_mcstructure_file_in_redstone_CD",
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -1,126 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import os
from typing import Literal
from ...exceptions import CommandFormatError
from ...main import MidiConvert
from ..main import ConvertConfig
from ..mcstructure import commands_to_structure,commands_to_redstone_delay_structure
def to_mcstructure_file_in_delay(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
max_height: int = 64,
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
player: str
玩家选择器,默认为`@a`
max_height: int
生成结构最大高度
Returns
-------
tuple[tuple[int,]结构大小, int音乐总延迟]
"""
if midi_cvt.enable_old_exe_format:
raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。")
cmd_list, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
struct, size, end_pos = commands_to_structure(cmd_list, max_height - 1)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.mcstructure")
),
"wb+",
) as f:
struct.dump(f)
return size, max_delay
def to_mcstructure_file_in_redstone_CD(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
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对象
data_cfg: ConvertConfig 对象
部分转换通用参数
player: str
玩家选择器,默认为`@a`
axis_side: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"]
生成结构的延展方向
basement_block: str
结构的基底方块
Returns
-------
tuple[tuple[int,]结构大小, int音乐总延迟]
"""
if midi_cvt.enable_old_exe_format:
raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。")
cmd_list, max_delay, max_multiple_cmd = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
)
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
struct, size, end_pos = commands_to_redstone_delay_structure(cmd_list,max_delay,max_multiple_cmd, basement_block, axis_side)
with open(
os.path.abspath(
os.path.join(data_cfg.dist_path, f"{midi_cvt.midi_music_name}.mcstructure")
),
"wb+",
) as f:
struct.dump(f)
return size, max_delay

View File

@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以生成结构附加包的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_mcstructure_addon_in_delay"
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -1,205 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import json
import os
import shutil
from TrimMCStruct import Structure
from ...exceptions import CommandFormatError
from ...main import MidiConvert
from ..archive import behavior_mcpack_manifest, compress_zipfile
from ..main import ConvertConfig
from ..mcstructure import commands_to_structure, form_command_block_in_NBT_struct
def to_mcstructure_addon_in_delay(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
max_height: int = 64,
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件后打包成附加包并在附加包中生成相应地导入函数
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
data_cfg: ConvertConfig 对象
部分转换通用参数
player: str
玩家选择器,默认为`@a`
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
if midi_cvt.enable_old_exe_format:
raise CommandFormatError("使用mcstructure结构文件导出时不支持旧版本的指令格式。")
command_list, max_delay = midi_cvt.to_command_list_in_delay(
data_cfg.volume_ratio,
data_cfg.speed_multiplier,
player,
)[:2]
if not os.path.exists(data_cfg.dist_path):
os.makedirs(data_cfg.dist_path)
# 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建
if os.path.exists(f"{data_cfg.dist_path}/temp/"):
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
os.makedirs(f"{data_cfg.dist_path}/temp/functions/")
os.makedirs(f"{data_cfg.dist_path}/temp/structures/")
# 写入manifest.json
with open(f"{data_cfg.dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.midi_music_name} 音乐播放包MCSTRUCTURE(MCPACK) 延迟播放器 - 由 音·创 生成",
pack_name=midi_cvt.midi_music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
),
fp=f,
indent=4,
)
# 将命令列表写入文件
index_file = open(
f"{data_cfg.dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
struct, size, end_pos = commands_to_structure(command_list, max_height - 1)
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_main.mcstructure",
)
),
"wb+",
) as f:
struct.dump(f)
del struct
if data_cfg.progressbar_style:
scb_name = midi_cvt.midi_music_name[:3] + "Pgb"
index_file.write("scoreboard objectives add {0} dummy {0}\n".format(scb_name))
struct_a = Structure(
(1, 1, 1),
)
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="显示进度条并加分",
),
)
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_start.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
index_file.write(f"structure load {midi_cvt.midi_music_name}_start ~ ~ ~1\n")
pgb_struct, pgbSize, pgbNowPos = commands_to_structure(
midi_cvt.form_progress_bar(max_delay, scb_name, data_cfg.progressbar_style),
max_height - 1,
)
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_pgb.mcstructure",
)
),
"wb+",
) as f:
pgb_struct.dump(f)
index_file.write(f"structure load {midi_cvt.midi_music_name}_pgb ~ ~1 ~1\n")
struct_a = Structure(
(1, 1, 1),
)
struct_a.set_block(
(0, 0, 0),
form_command_block_in_NBT_struct(
r"scoreboard players reset {} {}".format(player, scb_name),
(0, 0, 0),
1,
0,
alwaysRun=False,
customName="重置进度条计分板",
),
)
with open(
os.path.abspath(
os.path.join(
data_cfg.dist_path,
"temp/structures/",
f"{midi_cvt.midi_music_name}_reset.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
del struct_a, pgb_struct
index_file.write(
f"structure load {midi_cvt.midi_music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
)
index_file.write(
f"structure load {midi_cvt.midi_music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
)
else:
index_file.write(f"structure load {midi_cvt.midi_music_name}_main ~ ~ ~1\n")
index_file.close()
if os.path.exists(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack"):
os.remove(f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack")
compress_zipfile(
f"{data_cfg.dist_path}/temp/",
f"{data_cfg.dist_path}/{midi_cvt.midi_music_name}.mcpack",
)
shutil.rmtree(f"{data_cfg.dist_path}/temp/")
return len(command_list), max_delay

View File

@@ -1,391 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关MCSTRUCTURE结构操作的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import List, Literal, Tuple
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
from ..constants import x, y, z
from ..subclass import SingleCommand
from .common import bottem_side_length_of_smallest_square_bottom_box
import struct
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,
},
}
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,
):
"""生成音符盒方块
:param note: `int`(0~24)
音符的音高
:param coordinate: `tuple[int,int,int]`
此方块所在之相对坐标
:param instrument: `str`
音符盒的乐器
:param powered: `bool`
是否已被激活
:return 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
},
)
def form_repeater_in_NBT_struct(delay: int, facing: int):
"""生成中继器方块
:param facing: 朝向:
Z- 北 0
X- 东 1
Z+ 南 2
X+ 西 3
:param delay: 0~3
:return Block()"""
return Block(
"minecraft",
"unpowered_repeater",
{
"repeater_delay": delay,
"direction": facing,
},
)
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,
):
"""
使用指定项目返回指定的指令方块结构
:param command: `str`
指令
:param coordinate: `tuple[int,int,int]`
此方块所在之相对坐标
:param particularValue:
方块特殊值,即朝向
:0 下 无条件
:1 上 无条件
:2 z轴负方向 无条件
:3 z轴正方向 无条件
:4 x轴负方向 无条件
:5 x轴正方向 无条件
:6 下 无条件
:7 下 无条件
:8 下 有条件
:9 上 有条件
:10 z轴负方向 有条件
:11 z轴正方向 有条件
:12 x轴负方向 有条件
:13 x轴正方向 有条件
:14 下 有条件
:14 下 有条件
注意此处特殊值中的条件会被下面condition参数覆写
:param impluse: `int 0|1|2`
方块类型
0脉冲 1循环 2连锁
:param condition: `bool`
是否有条件
:param alwaysRun: `bool`
是否始终执行
:param tickDelay: `int`
执行延时
:param customName: `str`
悬浮字
:param executeOnFirstTick: `bool`
首刻执行(循环指令方块是否激活后立即执行若为False则从激活时起延迟后第一次执行)
:param trackOutput: `bool`
是否输出
:return:str
"""
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,
"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=17959425,
)
def commands_to_structure(
commands: List[SingleCommand],
max_height: int = 64,
):
"""
:param commands: 指令列表
:param max_height: 生成结构最大高度
:return 结构类,结构占用大小,终点坐标
"""
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
len(commands), max_height
)
struct = Structure(
(_sideLength, max_height, _sideLength), # 声明结构大小
)
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,
),
)
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[SingleCommand],
delay_length: int,
max_multicmd_length: int,
base_block: str = "concrete",
axis_: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"] = "z+",
) -> Tuple[Structure, Tuple[int, int, int], Tuple[int, int, int]]:
"""
:param commands: 指令列表
:param delay_length: 延时总长
:param max_multicmd_length: 最大同时播放的音符数量
:param base_block: 生成结构的基底方块
:param axis_: 生成结构的延展方向
:return 结构类,结构占用大小,终点坐标
"""
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)
struct = Structure(
(round(delay_length/2+0.5+len(commands)) if extensioon_direction == x else max_multicmd_length, 3, round(delay_length/2+0.5+len(commands)) if extensioon_direction == z else max_multicmd_length)
)
pos_now = {x:(0 if forward else struct.size[0]),y:0,z:(0 if forward else struct.size[2])}
first_impluse = True
for cmd in commands:
single_repeater_value = round(cmd.delay / 2) % 4 - 1
additional_repeater = round(cmd.delay / 2)// 4
for i in range(additional_repeater):
struct.set_block(
tuple(pos_now.values()),
Block("minecraft", base_block,),
)
struct.set_block(
(pos_now[x],1,pos_now[z]),
form_repeater_in_NBT_struct(
delay=3,
facing=repeater_facing,
),
)
pos_now[extensioon_direction] += goahead
first_impluse = True
if single_repeater_value >= 0:
struct.set_block(
tuple(pos_now.values()),
Block("minecraft", base_block,),
)
struct.set_block(
(pos_now[x],1,pos_now[z]),
form_repeater_in_NBT_struct(
delay=single_repeater_value,
facing=repeater_facing,
),
)
pos_now[extensioon_direction] += goahead
first_impluse = True
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=0,
customName=cmd.annotation_text,
),
)
struct.set_block(
(pos_now[x],2,pos_now[z]),
Block("minecraft", "redstone_wire"),
)
pos_now[extensioon_direction] += goahead
first_impluse = False
return struct, struct.size, tuple(pos_now.values())

View File

@@ -1,116 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关红石音乐生成操作的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from ..exceptions import NotDefineProgramError, ZeroSpeedError
from ..main import MidiConvert
from ..subclass import SingleCommand
# 你以为写完了吗?其实并没有
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 = (
midi_cvt.perc_inst_to_soundID_withX(InstID)
if SpecialBits
else midi_cvt.inst_to_souldID_withX(InstID)
)
except UnboundLocalError as E:
soundID, _X = (
midi_cvt.perc_inst_to_soundID_withX(-1)
if SpecialBits
else midi_cvt.inst_to_souldID_withX(-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)]

View File

@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关Schematic结构生成的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import nbtschematic

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以生成Schematic结构的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
]
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from ..schematic import *

View File

@@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关WebSocket服务器操作的内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import fcwslib
# 这个库有问题,正在检修
class Plugin(fcwslib.Plugin):
async def on_connect(self) -> None:
print('对象已被连接')
await self.send_command('list', callback=self.list)
await self.subscribe('PlayerMessage', callback=self.player_message)
async def on_disconnect(self) -> None:
print('对象停止连接')
async def on_receive(self, response) -> None:
print('已接收非常规回复 {}'.format(response))
async def list(self, response) -> None:
print('已收取指令执行回复 {}'.format(response))
async def player_message(self, response) -> None:
print('已收取玩家事件回复 {}'.format(response))

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以启动WebSocket服务器播放的附加功能
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = []
__author__ = (("金羿", "Eilles Wan"),)
from .main import *

View File

@@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import fcwslib
from ...main import MidiConvert
from ..main import ConvertConfig
from ...subclass import SingleCommand
def open_websocket_server(
midi_cvt: MidiConvert,
data_cfg: ConvertConfig,
player: str = "@a",
server_dist: str = "localhost",
server_port: int = 8000,
):
wssever = fcwslib.Server(server=server_dist,port=server_port,debug_mode=False)

View File

@@ -1,383 +0,0 @@
# -*- coding: utf-8 -*-
"""
旧版本转换功能以及已经弃用的函数
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import Dict, List, Tuple, Union
from .exceptions import *
from .main import MidiConvert, mido
from .subclass import *
from .utils import *
class ObsoleteMidiConvert(MidiConvert):
"""
我说一句话:
这些破烂老代码能跑得起来就是谢天谢地,你们还指望我怎么样?这玩意真的不会再维护了,我发誓!
"""
def to_command_list_method1(
self,
scoreboard_name: str = "mscplay",
MaxVolume: float = 1.0,
speed: float = 1.0,
) -> list:
"""
使用Dislink Sforza的转换思路将midi转换为我的世界命令列表
:param scoreboard_name: 我的世界的计分板名称
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:return: tuple(命令列表, 命令个数, 计分板最大值)
"""
# :param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
tracks = []
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
if not self.midi:
raise MidiUnboundError(
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象这是不可复用的。"
)
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
commands = 0
maxscore = 0
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
# 分轨的思路其实并不好,但这个算法就是这样
# 所以我建议用第二个方法 _toCmdList_m2
for i, track in enumerate(self.midi.tracks):
ticks = 0
instrumentID = 0
singleTrack = []
for msg in track:
ticks += msg.time
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
else:
if msg.type == "program_change":
instrumentID = msg.program
if msg.type == "note_on" and msg.velocity != 0:
nowscore = round(
(ticks * tempo)
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
)
maxscore = max(maxscore, nowscore)
if msg.channel == 9:
soundID, _X = self.perc_inst_to_soundID_withX(instrumentID)
else:
soundID, _X = self.inst_to_souldID_withX(instrumentID)
singleTrack.append(
"execute @a[scores={"
+ str(scoreboard_name)
+ "="
+ str(nowscore)
+ "}"
+ f"] ~ ~ ~ playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
f"{2 ** ((msg.note - 60 - _X) / 12)}"
)
commands += 1
if len(singleTrack) != 0:
tracks.append(singleTrack)
return [tracks, commands, maxscore]
def _toCmdList_m1(
self,
scoreboardname: str = "mscplay",
volume: float = 1.0,
speed: float = 1.0) -> list:
"""
使用Dislink Sforza的转换思路将midi转换为我的世界命令列表
:param scoreboardname: 我的世界的计分板名称
:param volume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:return: tuple(命令列表, 命令个数, 计分板最大值)
"""
tracks = []
if volume > 1:
volume = 1
if volume <= 0:
volume = 0.001
commands = 0
maxscore = 0
for i, track in enumerate(self.midi.tracks):
ticks = 0
instrumentID = 0
singleTrack = []
for msg in track:
ticks += msg.time
# print(msg)
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
else:
if msg.type == "program_change":
# print("TT")
instrumentID = msg.program
if msg.type == "note_on" and msg.velocity != 0:
nowscore = round(
(ticks * tempo)
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
)
maxscore = max(maxscore, nowscore)
soundID, _X = self.__Inst2soundID_withX(instrumentID)
singleTrack.append(
"execute @a[scores={" +
str(scoreboardname) +
"=" +
str(nowscore) +
"}" +
f"] ~ ~ ~ playsound {soundID} @s ~ ~{1 / volume - 1} ~ {msg.velocity * (0.7 if msg.channel == 0 else 0.9)} {2 ** ((msg.note - 60 - _X) / 12)}")
commands += 1
if len(singleTrack) != 0:
tracks.append(singleTrack)
return [tracks, commands, maxscore]
# 原本这个算法的转换效果应该和上面的算法相似的
def _toCmdList_m2(
self: MidiConvert,
scoreboard_name: str = "mscplay",
MaxVolume: float = 1.0,
speed: float = 1.0,
) -> tuple:
"""
使用神羽和金羿的转换思路将midi转换为我的世界命令列表
:param scoreboard_name: 我的世界的计分板名称
:param MaxVolume: 音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:return: tuple(命令列表, 命令个数, 计分板最大值)
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
tracks = []
cmdAmount = 0
maxScore = 0
InstID = -1
self.to_music_channels()
# 此处 我们把通道视为音轨
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
if i == 9:
SpecialBits = True
else:
SpecialBits = False
nowTrack = []
for track_no, track in self.channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
self.perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else self.inst_to_souldID_withX(InstID)
)
score_now = round(msg[-1] / float(speed) / 50)
maxScore = max(maxScore, score_now)
nowTrack.append(
self.execute_cmd_head.format(
"@a[scores=({}={})]".format(scoreboard_name, score_now)
.replace("(", r"{")
.replace(")", r"}")
)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
cmdAmount += 1
if nowTrack:
tracks.append(nowTrack)
return tracks, cmdAmount, maxScore
def _toCmdList_withDelay_m1(
self: MidiConvert,
MaxVolume: float = 1.0,
speed: float = 1.0,
player: str = "@a",
) -> list:
"""
使用Dislink Sforza的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟
:param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:param player: 玩家选择器,默认为`@a`
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
"""
tracks = {}
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
if not self.midi:
raise MidiUnboundError(
"你是否正在使用的是一个由 copy_important 生成的MidiConvert对象这是不可复用的。"
)
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
tempo = mido.midifiles.midifiles.DEFAULT_TEMPO
for i, track in enumerate(self.midi.tracks):
instrumentID = 0
ticks = 0
for msg in track:
ticks += msg.time
if msg.is_meta:
if msg.type == "set_tempo":
tempo = msg.tempo
else:
if msg.type == "program_change":
instrumentID = msg.program
if msg.type == "note_on" and msg.velocity != 0:
now_tick = round(
(ticks * tempo)
/ ((self.midi.ticks_per_beat * float(speed)) * 50000)
)
soundID, _X = self.inst_to_souldID_withX(instrumentID)
try:
tracks[now_tick].append(
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
f"{2 ** ((msg.note - 60 - _X) / 12)}"
)
except KeyError:
tracks[now_tick] = [
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg.velocity / 128} "
f"{2 ** ((msg.note - 60 - _X) / 12)}"
]
results = []
all_ticks = list(tracks.keys())
all_ticks.sort()
for i in range(len(all_ticks)):
if i != 0:
for j in range(len(tracks[all_ticks[i]])):
if j != 0:
results.append((tracks[all_ticks[i]][j], 0))
else:
results.append(
(tracks[all_ticks[i]][j], all_ticks[i] - all_ticks[i - 1])
)
else:
for j in range(len(tracks[all_ticks[i]])):
results.append((tracks[all_ticks[i]][j], all_ticks[i]))
return [results, max(all_ticks)]
def _toCmdList_withDelay_m2(
self: MidiConvert,
MaxVolume: float = 1.0,
speed: float = 1.0,
player: str = "@a",
) -> list:
"""
使用神羽和金羿的转换思路将midi转换为我的世界命令列表并输出每个音符之后的延迟
:param MaxVolume: 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放音频
:param speed: 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
:param player: 玩家选择器,默认为`@a`
:return: 全部指令列表[ ( str指令, int距离上一个指令的延迟 ),...]
"""
tracks = {}
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
MaxVolume = 1 if MaxVolume > 1 else (0.001 if MaxVolume <= 0 else MaxVolume)
InstID = -1
self.to_music_channels()
results = []
for i in self.channels.keys():
# 如果当前通道为空 则跳过
if not self.channels[i]:
continue
if i == 9:
SpecialBits = True
else:
SpecialBits = False
for track_no, track in self.channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
soundID, _X = (
self.perc_inst_to_soundID_withX(msg[1])
if SpecialBits
else self.inst_to_souldID_withX(InstID)
)
score_now = round(msg[-1] / float(speed) / 50)
try:
tracks[score_now].append(
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
except KeyError:
tracks[score_now] = [
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
]
all_ticks = list(tracks.keys())
all_ticks.sort()
for i in range(len(all_ticks)):
for j in range(len(tracks[all_ticks[i]])):
results.append(
(
tracks[all_ticks[i]][j],
(
0
if j != 0
else (
all_ticks[i] - all_ticks[i - 1]
if i != 0
else all_ticks[i]
)
),
)
)
return [results, max(all_ticks)]

View File

@@ -1,279 +0,0 @@
# -*- coding: utf-8 -*-
"""
存储许多非主要的相关类
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from dataclasses import dataclass
from typing import Optional
from .constants import PERCUSSION_INSTRUMENT_LIST
@dataclass(init=False)
class SingleNote:
"""存储单个音符的类"""
instrument: int
"""乐器编号"""
note: int
"""音符编号"""
velocity: int
"""力度/响度"""
start_time: int
"""开始之时 ms"""
duration: int
"""音符持续时间 ms"""
track_no: int
"""音符所处的音轨"""
def __init__(
self,
instrument: int,
pitch: int,
velocity: int,
startTime: int,
lastTime: int,
track_number: int = 0,
):
"""用于存储单个音符的类
:param instrument 乐器编号
:param pitch 音符编号
:param velocity 力度/响度
:param startTime 开始之时(ms)
注:此处的时间是用从乐曲开始到当前的毫秒数
:param lastTime 音符延续时间(ms)"""
self.instrument: int = instrument
"""乐器编号"""
self.note: int = pitch
"""音符编号"""
self.velocity: int = velocity
"""力度/响度"""
self.start_time: int = startTime
"""开始之时 ms"""
self.duration: int = lastTime
"""音符持续时间 ms"""
self.track_no: int = track_number
"""音符所处的音轨"""
@property
def inst(self):
"""乐器编号"""
return self.instrument
@inst.setter
def inst(self, inst_):
self.instrument = inst_
@property
def pitch(self):
"""音符编号"""
return self.note
def __str__(self):
return (
f"Note(inst = {self.inst}, pitch = {self.note}, velocity = {self.velocity}, "
f"startTime = {self.start_time}, lastTime = {self.duration}, )"
)
def __tuple__(self):
return self.inst, self.note, self.velocity, self.start_time, self.duration
def __dict__(self):
return {
"inst": self.inst,
"pitch": self.note,
"velocity": self.velocity,
"startTime": self.start_time,
"lastTime": self.duration,
}
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()
@dataclass(init=False)
class SingleCommand:
"""存储单个指令的类"""
command_text: str
"""指令文本"""
conditional: bool
"""执行是否有条件"""
delay: int
"""执行的延迟"""
annotation_text: str
"""指令注释"""
def __init__(
self,
command: str,
condition: bool = False,
tick_delay: int = 0,
annotation: str = "",
):
"""
存储单个指令的类
Parameters
----------
command: str
指令
condition: bool
是否有条件
tick_delay: int
执行延时
annotation: str
注释
"""
self.command_text = command
self.conditional = condition
self.delay = tick_delay
self.annotation_text = annotation
def copy(self):
return SingleCommand(
command=self.command_text,
condition=self.conditional,
tick_delay=self.delay,
annotation=self.annotation_text,
)
@property
def cmd(self) -> str:
"""
我的世界函数字符串(包含注释)
"""
return self.__str__()
def __str__(self) -> str:
"""
转为我的世界函数文件格式(包含注释)
"""
return "#[{cdt}]<{delay}> {ant}\n{cmd}".format(
cdt="CDT" if self.conditional else "",
delay=self.delay,
ant=self.annotation_text,
cmd=self.command_text,
)
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()
@dataclass(init=False)
class SingleNoteBox:
"""存储单个音符盒"""
instrument_block: str
"""乐器方块"""
note_value: int
"""音符盒音高"""
annotation_text: str
"""音符注释"""
is_percussion: bool
"""是否为打击乐器"""
def __init__(
self,
instrument_block_: str,
note_value_: int,
percussion: Optional[bool] = None,
annotation: str = "",
):
"""用于存储单个音符盒的类
:param instrument_block_ 音符盒演奏所使用的乐器方块
:param note_value_ 音符盒的演奏音高
:param percussion 此音符盒乐器是否作为打击乐处理
注:若为空,则自动识别是否为打击乐器
:param annotation 音符注释"""
self.instrument_block = instrument_block_
"""乐器方块"""
self.note_value = note_value_
"""音符盒音高"""
self.annotation_text = annotation
"""音符注释"""
if percussion is None:
self.is_percussion = percussion in PERCUSSION_INSTRUMENT_LIST
else:
self.is_percussion = percussion
@property
def inst(self) -> str:
"""获取音符盒下的乐器方块"""
return self.instrument_block
@inst.setter
def inst(self, inst_):
self.instrument_block = inst_
@property
def note(self) -> int:
"""获取音符盒音调特殊值"""
return self.note_value
@note.setter
def note(self, note_):
self.note_value = note_
@property
def annotation(self) -> str:
"""获取音符盒的备注"""
return self.annotation_text
@annotation.setter
def annotation(self, annotation_):
self.annotation_text = annotation_
def copy(self):
return SingleNoteBox(
instrument_block_=self.instrument_block,
note_value_=self.note_value,
annotation=self.annotation_text,
)
def __str__(self) -> str:
return f"Note(inst = {self.inst}, note = {self.note}, )"
def __tuple__(self) -> tuple:
return self.inst, self.note, self.annotation
def __dict__(self) -> dict:
return {
"inst": self.inst,
"note": self.note,
"annotation": self.annotation,
}
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()

View File

@@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放主程序所必须的功能性内容
"""
"""
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿穆组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import Any, Dict
def mctick2timestr(mc_tick: int) -> str:
"""
将《我的世界》的游戏刻计转为表示时间的字符串
"""
return str(int(int(mc_tick / 20) / 60)) + ":" + str(int(int(mc_tick / 20) % 60))
def empty_midi_channels(channel_count: int = 17, staff: Any = {}) -> Dict[int, Any]:
"""
空MIDI通道字典
"""
return dict(
(
i,
(staff.copy() if isinstance(staff, (dict, list)) else staff),
) # 这告诉我们你不能忽略任何一个复制的序列因为它真的我哭死折磨我一整天全在这个bug上了
for i in range(channel_count)
)

138
README.md
View File

@@ -1,129 +1,49 @@
<h1 align="center">
音·创 Musicreater
</h1>
# 音·创 Musicreater
<p align="center">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.ico">
</img>
</p>
### 介绍
音·创(Musicreater)是由金羿(W-YI)开发的一款《我的世界》基岩版音乐生成辅助软件
<h3 align="center">一款免费开源的《我的世界》数字音频转换库。</h3>
欢迎加群861684859
<p align="center">
<img src="https://forthebadge.com/images/badges/built-with-love.svg">
<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>
### 软件架构
[![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
[![][Bilibili: 诸葛亮与八卦阵]](https://space.bilibili.com/604072474)
[![CodeStyle: black]](https://github.com/psf/black)
[![][python]](https://www.python.org/)
[![][license]](LICENSE)
[![][release]](../../releases)
软件采用Python作为第一语言目前还没有使用其他语言辅助。使用BeeWare作为图形库兼容安卓。
[![GiteeStar](https://gitee.com/TriM-Organization/Musicreater/badge/star.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/stargazers)
[![GiteeFork](https://gitee.com/TriM-Organization/Musicreater/badge/fork.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/members)
[![GitHub Repo stars](https://img.shields.io/github/stars/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/stargazers)
[![GitHub Repo Forks](https://img.shields.io/github/forks/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/forks)
尽量全平台支持
简体中文 🇨🇳 | [English🇬🇧](README_EN.md)
## 介绍 🚀
### 安装教程
音·创 是一个免费开源的针对 **《我的世界》** 的 MIDI 音乐转换库
#### Windows
欢迎加群:[861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
即将到来。
## 安装 🔳
#### Linux
- 使用 pypi
即将到来。
```bash
pip install Musicreater
```
#### Android
- 如果出现错误,可以尝试:
```bash
pip install -i https://pypi.python.org/simple Musicreater
```
即将到来。
- 升级:
### 使用说明
```bash
pip install -i https://pypi.python.org/simple Musicreater --upgrade
```
1. 直接运行就好
2. 有不懂的问题来群里问
3. 请理解英文表述
- 克隆仓库并安装
```bash
git clone https://gitee.com/TriM-Organization/Musicreater.git
cd Musicreater
python setup.py install
```
### 致谢
以上命令中 `python`、`pip` 请依照各个环境不同灵活更换,可能为`python3`或`pip3`之类。
1. 感谢由 [Fuckcraft](https://github.com/fuckcraft) “鸣凤鸽子”等 带来的我的世界websocket服务器功能
2. 感谢 昀梦QQ1515399885 找出指令生成错误bug并指正
3. 感谢由 Charlie_Ping “查理平” 带来的bdx转换功能
4. 感谢由 CMA_2401PT 提供的 BDXWorkShop作为.bdx结构的操作指导
5. 感谢广大群友为此程序提供的测试等支持
6. 若您为我找出了错误但您的名字没有显示在此列表中,请联系我!
## 文档 📄
[生成文件的使用](./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)
## 作者 ✒
金羿 Eilles我的世界基岩版指令师个人开发者B 站不知名 UP 主,江西在校高中生。
诸葛亮与八卦阵 bgArray我的世界基岩版玩家喜欢编程和音乐深圳初二学生。
## 致谢 🙏
本致谢列表排名无顺序。
- 感谢 **昀梦**\<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/)给我们的开发与更新带来巨大的压力和动力,让我们在原本一骑绝尘的摸鱼道路上转向开发,希望他能考上一个理想的大学!
- 感谢 **Touch “偷吃”**\<QQ1793537164\> 提供的 BDX 导入测试支持,并对程序的改进提供了丰富的意见;同时也感谢他的不断尝试新的内容,使我们的排错更进一步
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题,辅助我们找到了视窗操作系统下的兼容性问题;感谢其反馈延迟播放器出现的重大问题,让我们得以修改全部延迟播放错误;尤其感谢他对于我们的软件的大力宣传
- 感谢 **Ammelia “艾米利亚”**\<QQ2838334637\> 敦促我们进行新的功能开发,并为新功能提出了非常优秀的大量建议,以及提供的 BDX 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
- 感谢 **[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”** 对我们项目的支持与宣传,希望他能考得一所优秀的大学!
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误,并提醒我们修复一个一直存在的大 bug。
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误,并提醒修复 bug。
- 感谢 **小埋**\<2039310975\> 反馈附加包生成时缺少描述和标题的问题。
> 感谢广大群友为此程序提供的测试等支持
>
> 若您对我们有所贡献但您的名字没有显示在此列表中,请联系我们!
## 联系 📞
若遇到库中的问题,欢迎在[](https://gitee.com/TriM-Organization/Musicreater/issues/new)提出你的 issue。
如果需要与开发组进行交流,欢迎加入我们的[开发闲聊 Q 群](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)。
---
此项目并非一个官方 《我的世界》_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.
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-00A1E7?style=for-the-badge
[Bilibili: 诸葛亮与八卦阵]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge
1. QQ 2647547478
2. 电邮 EillesWan2006@163.com W-YI_DoctorYI@outlook.com
3. 微信 WYI_DoctorYI

12
README.rst Normal file
View 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

View File

@@ -1,131 +0,0 @@
<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 open-source library of converting digital music files into <i>Minecraft</i> formats.</h3>
<p align="center">
<img src="https://forthebadge.com/images/badges/built-with-love.svg">
</img>
<p>
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
[![][Bilibili: bgArray]](https://space.bilibili.com/604072474)
[![CodeStyle: black]](https://github.com/psf/black)
[![][python]](https://www.python.org/)
[![][license]](LICENSE)
[![][release]](../../releases)
[![GiteeStar](https://gitee.com/TriM-Organization/Musicreater/badge/star.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/stargazers)
[![GiteeFork](https://gitee.com/TriM-Organization/Musicreater/badge/fork.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/members)
[![GitHub Repo stars](https://img.shields.io/github/stars/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/stargazers)
[![GitHub Repo Forks](https://img.shields.io/github/forks/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/forks)
[简体中文 🇨🇳](README.md) | English🇬🇧
**Notice that the language translation of _Musicreater_ may be a little SLOW.**
## Introduction🚀
Musicreater is a free open-source library used for converting digital music files into formats that could be read in _Minecraft_.
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
## Installation 🔳
- Via pypi
```bash
pip install Musicreater
```
- If not work, also try:
```bash
pip install -i https://pypi.python.org/simple Musicreater
```
- Update:
```bash
pip install -i https://pypi.python.org/simple Musicreater --upgrade
```
- Clone repo and Install:
```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 senior high school student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
bgArray "诸葛亮与八卦阵": A junior high school student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
## Thanks🙏
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. We hope he can get into a good university as he wantted to!
- Thank _Touch “偷吃”_\<QQ1793537164\> for support of debugging and testing program and algorithm, as well his/her suggestions to the improvement of 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. Hope he could get into a good university!
- 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.
> Thanks for a lot of groupmates' support and help
>
> If you have given contribution but haven't been in the list, please contact us!
## Contact Us📞
Meet problems? Welcome to give out your issue [here](https://github.com/EillesWan/Musicreater/issues/new)!
Want to get in contact of developers? Welcome to join our [Chat QQ group](https://jq.qq.com/?_wv=1027&k=hpeRxrYr).
---
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”等相关称呼均为引用性使用
- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易网络科技发展有限公司
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-00A1E7?style=for-the-badge
[Bilibili: bgArray]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-Apache-228B22?style=for-the-badge

52
README_en.md Normal file
View 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

View File

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

1
Run in devmode.bat Normal file
View File

@@ -0,0 +1 @@
briefcase dev

View 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("无法清除日志及临时文件")

View File

@@ -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("Find the full path of .egg-info folder"):
egg_info: list = []
for file in os.listdir():
if file.endswith(".egg-info"):
egg_info.append(file)
console.print(file)
for file in track(["build", "dist", "logs", *egg_info], description="Deleting files"):
if os.path.isdir(file) and os.access(file, os.W_OK):
shutil.rmtree(file)
if __name__ == "__main__":
main()

View File

@@ -1,281 +0,0 @@
<h1 align="center">音·创 Musicreater</h1>
<h2 align="center">库版 Package Version</h2>
<p align="center">
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.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, # 进度条样式组(详见下方)
)
# 懒得写了,等哪天有空写吧,后面的没用,是老的。
```
# 生成文件结构
## 名词解释
|名词|解释|备注|
|--------|-----------|----------|
|指令区|一个用于放置指令系统的区域,通常是常加载区。|常见于服务器指令系统、好友联机房间中|
|指令链(链)|与链式指令方块不同,一个指令链通常指代的是一串由某种非链式指令方块作为开头,后面连着一串链式指令方块的结构。|通常的链都应用于需要“单次激活而多指令”的简单功能|
|起始块|链最初的那个非链式指令方块。|此方块为脉冲方块或重复方块皆可|
|指令系统(系统)|指令系统通常指的是,由一个或多个指令链以及相关红石机构相互配合、一同组成的,为达到某种特定的功能而构建的整体结构。|通常的系统都应用于需要“综合调配指令”的复杂功能。可由多个实现不同功能的模块构成,不同系统之间可以相互调用各自的模块。|
|游戏刻(刻)|游戏的一刻是指《我的世界》的游戏循环运行一次所占用的时间。([详见《我的世界》中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E6%B8%B8%E6%88%8F%E5%88%BB))。指令方块的延迟功能(即指令方块的“延迟刻数”设置项,此项的名称被误译为“已选中项的延迟”)的单位即为`1`游戏刻。|正常情况下游戏固定以每秒钟20刻的速率运行。但是由于游戏内的绝大多数操作都是基于刻数而非现实中的时间来计时并进行的一次游戏循环内也许会发生大量的操作很多情况下一秒对应的游戏刻会更少。 |
## 文件格式
1. 附加包格式(`.mcpack`
使用附加包格式导出音乐,则音乐会以指令函数文件(`.mcfunction`)存储于附加包内。在附加包中,函数文件的存储结构应为:
- `functions\`
- `index.mcfunction`
- `mscply\`
- `progressShow.mcfunction`
- `track1.mcfunction`
- `track2.mcfunction`
- ...
- `trackN.mcfunction`
如图,其中,`index.mcfunction`文件和`mscply`文件夹存在于函数目录的根下;在`mscply`目录中,包含音乐导出的众多音轨播放文件(`trackX.mcfunction`),同时,若生成此包时选择了带有进度条的选项,则会包含`progressShow.mcfunction`文件。
`index.mcfunction`用于开始播放,其中包含打开各个音轨对应函数的指令,以及加分指令,这里的加分,是将**播放计分板的值大于等于`1`**的所有**玩家**的播放计分板分数增加`1`。同时,若生成此包时选择了自动重置计分板的选项,则会包含一条重置计分板的指令。
> 你知道吗?音·创的最早期版本“《我的世界》函数音乐生成器”正是用函数来播放,不过这个版本采取的读入数据的形式大有不同。
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`。如此往复,直至指令链堆叠完成。
## 播放器
以结构生成的文件可以采用多种方式播放,一类播放方式,我们称其为**播放器**,例如**延迟播放器**和**计分板播放器**等等,以后推出的新的播放器,届时也会在此处更新。
为什么要设计这么多播放器?是为了适应不同的播放环境需要。通常情况下,一个音乐中含有多个音符,音符与音符之间存在间隔,这里就产生了不一样的,实现音符间时间间隔的方式。而不同的应用环境下,又会产生不一样的要求。接下来将对不同的播放器进行详细介绍。
1. 计分板播放器
计分板播放器是一种传统的《我的世界》音乐播放方式。通过对于计分板加分来实现播放不同的音符。一个很简单的原理,就是**用不同的计分板分值对应不同的音符**,再通过加分,来达到那个分值,即播放出来。
在**音·创**中,用来达到这种效果的指令是这样的:
```mcfunction
execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
```
|参数|说明|备注|
|--------|-----------|----------|
|`ScBd`|指定的计分板名称||
|`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}$ |
后四个参数决定了这个音的性质,而前两个参数仅仅是为了决定音播放的时间。
2. 延迟播放器
延迟播放器是通过《我的世界》游戏中,指令方块的设置项“延迟刻数”来达到定位音符的效果。**将所有的音符依照其播放时距离乐曲开始时的时间(毫秒),放在一个序列内,再计算音符两两之间对应的时间差值,转换为《我的世界》内对应的游戏刻数之后填入指令方块的设置中。**
在音·创中,由于此方式播放的音乐不需要用计分板,所以播放指令是这样的:
```mcfunction
execute Tg ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
```
|参数|说明|备注|
|--------|-----------|----------|
|`Tg`|播放对象|选择器或玩家名|
|`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}$ |
其中后四个参数决定了这个音的性质。
由于这样的延迟数据是依赖于指令方块的设置项所以使用这种播放器所转换出的结果仅可以存储在包含方块NBT信息及方块实体NBT信息的结构文件中或者直接输出至世界。
# 进度条自定义
因为我们提供了可以自动转换进度条的功能,因此在这里给出进度条自定义参数的详细解释。
请注意,并非所有的演示样例程序都支持自定义进度条。
一个进度条,明显地,有**固定部分**和**可变部分**来构成。而可变部分又包括了文字和图形两种(当然,《我的世界》里头的进度条,可变的图形也就是那个“条”了)。这一点你需要了解,因为后文中包含了很多这方面的概念需要你了解。
进度条的自定义功能使用一个字符串来定义自己的样式,其中包含众多**标识符**来表示可变部分。
标识符如下(注意大小写):
| 标识符 | 指定的可变量 |
|---------|----------------|
| `%%N` | 乐曲名(即传入的文件名)|
| `%%s` | 当前计分板值 |
| `%^s` | 计分板最大值 |
| `%%t` | 当前播放时间 |
| `%^t` | 曲目总时长 |
| `%%%` | 当前进度比率 |
| `_` | 用以表示进度条占位|
表示进度条占位的 `_` 是用来标识你的进度条的。也就是可变部分的唯一的图形部分。
**样式定义字符串**的样例如下,这也是默认的进度条的样式:
`▶ %%N [ %%s/%^s %%% __________ %%t|%^t]`
这是单独一行的进度条,当然你也可以制作多行的,如果是一行的,输出时所使用的指令便是 `title`,而如果是多行的话,输出就会用 `titleraw` 作为进度条字幕。
哦对了,上面的只不过是样式定义,同时还需要定义的是可变图形的部分,也就是进度条上那个真正的“条”。
对于这个我们就采用了固定参数的方法,对于一个进度条,无非就是“已经播放过的”和“没播放过的”两种形态,所以,使用一个元组来传入这两个参数就是最简单的了。元组的格式也很简单:`(str: 播放过的部分长啥样, str: 没播放过的部分长啥样)` 。例如,我们默认的进度“条”的定义是这样的:
`('§e=§r', '§7=§r')`
综合起来,把这些参数传给函数需要一个参数整合,你猜用的啥?啊对对对,我用的还是元组!
我们的默认定义参数如下:
`(r'▶ %%N [ %%s/%^s %%% __________ %%t|%^t]',('§e=§r', '§7=§r'))`
*为了避免生成错误,请尽量避免使用标识符作为定义样式字符串的其他部分*

View File

@@ -1,58 +0,0 @@
<h1 align="center">音·创 Musicreater</h1>
<h2 align="center">库版 Package Version</h2>
<p align="center">
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
</p>
# 生成文件的使用
*由于先前的 **读我文件**(README.md) 过于冗杂,现另辟蹊径来给大家全方位的教程。*
*这是本库所生成文件的使用声明,不是使用本库的教程,若要查看**本库的大致文档**,可点击[此处](./%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. 若想要重置某实体的播放,可以将其播放用的计分板重置
> 其中 步骤三 和 步骤四 的顺序可以调换。
- 延迟播放器
1. 导入附加包
2. 在聊天框输入指令 `function index`
3. 同时激活所生成的循环和脉冲指令方块
> 需要注意的是,循环指令方块需要一直激活直到音乐结束
## 结构格式
支持的文件后缀:`.MCSTRUCTURE``.BDX`
1. 将结构导入世界
- 延迟播放器
2. 将结构生成的第一个指令方块之模式更改为**脉冲**
3. 激活脉冲方块
4. 若欲重置播放,可以停止对此链的激活,例如停止区块加载
5. 此播放器不支持暂停
- 计分板播放器
2. 在所生成的第一个指令方块前,放置一个循环指令方块,其朝向应当对着所生成的第一个方块
3. 在循环指令方块中输入使播放对象的播放用计分板加分的指令,延迟为 `0`,每次循环增加 `1`
4. 激活循环方块
5. 若想要暂停播放,可以停止循环指令方块的激活状态
6. 若想要重置某实体的播放,可以将其播放用的计分板重置

View File

@@ -1,198 +0,0 @@
**_注意本文档中的对照表版权归属于音·创作者并按照本仓库根目录下 LICENSE.md 中规定开源_**
**_使用时请遵循规定_**
- 版权所有 © 2023 音·创 开发者
- Copyright © 2023 all the developers of Musicreater
* 开源相关声明请见 仓库根目录下的 License.md
* Terms & Conditions: License.md in the root directory
音·创 开发交流群 861684859\
Email TriM-Organization@hotmail.com\
若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
# 乐音乐器
| 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 |

View File

@@ -1,135 +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**.
版权所有 © 2023 音·创 开发者
Copyright © 2023 all the developers of Musicreater
开源相关声明请见 ./License.md
Terms & Conditions: ./License.md
"""
import os
import Musicreater
from Musicreater.plugin import ConvertConfig
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
from Musicreater.plugin.funcpack import to_function_addon_in_score
from Musicreater.plugin.mcstructpack import to_mcstructure_addon_in_delay
# 获取midi列表
midi_path = input(f"请输入MIDI路径")
# 获取输出地址
out_path = input(f"请输入输出路径:")
# 选择输出格式
fileFormat = int(input(f"请输入输出格式[BDX(1) 或 MCPACK(0)]").lower())
playerFormat = int(input(f"请选择播放方式[计分板(1) 或 延迟(0)]").lower())
# 真假字符串判断
def bool_str(sth: str) -> bool:
try:
return bool(float(sth))
except ValueError:
if str(sth).lower() == "true":
return True
elif str(sth).lower() == "false":
return False
else:
raise ValueError("布尔字符串啊?")
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"作者名称:",
str,
)
if fileFormat == 1
else (),
()
if playerFormat == 1
else (
f"最大结构高度:",
int,
),
]:
if args:
prompts.append(args[1](input(args[0])))
print(f"正在处理 {midi_path} ")
cvt_mid = Musicreater.MidiConvert.from_midi_file(midi_path, old_exe_format=False)
cvt_cfg = ConvertConfig(out_path, *prompts[:3])
print(
" 指令总长:{},最高延迟:{}".format(
*(
to_function_addon_in_score(cvt_mid, cvt_cfg, *prompts[3:])
if playerFormat == 1
else to_mcstructure_addon_in_delay(cvt_mid, cvt_cfg, *prompts[3:])
)
)
if fileFormat == 0
else " 指令总长:{},最高延迟:{},结构大小{},终点坐标{}".format(
*(
to_BDX_file_in_score(cvt_mid, cvt_cfg, *prompts[3:])
if playerFormat == 1
else to_BDX_file_in_delay(cvt_mid, cvt_cfg, *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")

View File

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

View File

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

View File

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

View File

@@ -1,47 +0,0 @@
> 是谁把科技的领域布满政治的火药
>
> 是谁把纯净的蓝天染上暗淡的沉灰
>
> 中国人民无不热爱自己伟大的祖国
>
> 我们不会忘记屈辱历史留下的惨痛
>
> 我们希望世界和平
>
> 我们希望获得世界的尊重
>
> 愿世上再也没有战争
>
> 无论是热还是冷
>
> 无论是经济还是政治
>
> 让美妙的和平的优雅的音乐响彻世界
>
> ——金羿
> 2022 5 7
> Who has dropped political gunpowder into the technology
>
> Who has dyed clear blue sky into the dark grey
>
> All Chinese people love our great homeland
>
> We *WILL* remember the remain pain of the humiliating history
>
> We love the whole world but in peace
>
> We love everyone but under respect
>
> It is to be hoped that the war ends forever
>
> Whatever it is cold or hot
>
> Whatever it is economical or political
>
> Just let the wonderful music of peace surround the world
>
> ---- Eilles Wan
> 7/5 2022

49
pyproject.toml Normal file
View File

@@ -0,0 +1,49 @@
[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"
[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 = []
[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',
]
[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',
]
[tool.briefcase.app.musicreater.android]
requires = [
'toga-android>=0.3.0.dev20',
]

View File

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

View File

@@ -1,21 +0,0 @@
# 注意,这里是作者署名文件,文件格式开头为单子启
# 紧跟其后,不加空格留下常用名,常用名即常用网名
# 而在其后是各个语言下的名字。用 井字符 开头表示
# 注释,请注意,注释符号必须在一行之首否则无作用
# 每进行一次分段表示一个新的开发者,换行表示一个
# 新的语言。请全体开发者就此署名,谢谢!
启金羿
zh-CN 金羿
zh-TW 金羿
zh-ME 金羿羿喵
zh-HK 金 羿
en-GB Eilles Wan
en-US EillesWan
启诸葛亮与八卦阵
zh-CN 诸葛亮与八卦阵
zh-TW 諸葛亮與八卦陣
zh-ME 诸葛八卦喵
zh-HK 諸葛亮與八卦陣
en-GB Bagua Array
en-US bgArray

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 MiB

View File

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

1401
src/Musicreater.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
briefcase

View 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
View 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'][第几个音符][音符还是时间01] = 改成啥
# 修改音轨设置: 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__

View File

View File

@@ -0,0 +1,4 @@
from musicreater.app import main
if __name__ == '__main__':
main().main_loop()

133
src/musicreater/app.py Normal file
View 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()

View 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()

View 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

View 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文件之后无法正常退出的问题

View File

View File

@@ -0,0 +1,215 @@
import os
import brotli
'''感谢由 Charlie_Ping “查理平” 带来的bdx转换代码'''
class BdxConverter:
__header = "BD@"
__bin_header = b"BDX"
__generator_author = b"&Charlie_Ping"
keys = {
# x--, x++, addSmallX(-128~127), addX(-32768~32767), addBigX(-2147483648~2147483647)
"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"],
"end": b"\x58",
"isSigned": b"\x5a",
"placeCommandBlockWithData": b"\x1b",
"placeBlock": b"\x07"
}
def __init__(self, file_path: str, author: str, blocks):
self.author = author
self.blocks = blocks
self.file_path = file_path
self.direction = [0, 0, 0]
self.block_type = self.get_block_type
self.__file = self.create_and_upload_file
@property
def get_block_type(self):
"""
blocks
[
{
"direction": [x: int, y: int, z: int],
block_name: str,
particular_value: int,
}
]
:return: list 给出的所有方块种类名称
"""
block_type = set()
for block in self.blocks:
block_type.add(block["block_name"])
block_type = list(block_type)
return block_type
@property
def create_and_upload_file(self):
"""
瞎用property 害怕
创建一个bdx文件
要close
:return: 一个文件对象
"""
_dir = os.path.dirname(self.file_path)
if not os.path.isdir(_dir):
os.makedirs(_dir)
_bytes = self.__bin_header
_bytes += b"\x00"
_bytes += self.author.encode("utf-8") + self.__generator_author
for i in self.block_type:
_bytes += b"\x00\x01"
_bytes += bytes(i, encoding="utf-8")
_bytes += b"\x00"
_bytes += self.upload_blocks()
_bytes += b"X"
with open(self.file_path, "w+") as f:
f.write("BD@")
f.close()
with open(self.file_path, "ab+") as f:
f.write(brotli.compress(_bytes))
f.close()
return
def upload_blocks(self):
"""
计算差值
写入移动过程
写入方块
更新差值
:return:
"""
_types = b""
for block in self.blocks:
# print(f"当前方块:{block['block_name']}, 位置: {block['direction']}]")
diff = self.move_pointer(self.direction, block["direction"])
_types += diff
if block["block_name"] in ["command_block",
"chain_command_block",
"repeating_command_block"]:
_types += self.obtain_command_block(block)
else:
_types += self.obtain_universal_block(block)
self.direction = block["direction"]
return _types
def move_pointer(self, direction: list, new_direction):
"""
给出 两个[x, y, z]坐标返回pointer的移动过程
:param direction: 坐标 1
:param new_direction: 坐标 2
:return: bytes
"""
_bytes = b""
for i, sign in enumerate(["x", "y", "z"]):
# print(f"<{sign}> 新-旧={new_direction[i]-direction[i]}")
distance = new_direction[i] - direction[i]
if distance == 0:
# print("距离是0跳过了")
continue
_bytes += self.obtain_pointer_type(distance, sign)
# print(f"向 {sign} 运动了 {distance} 格子")
return _bytes
@classmethod
def obtain_pointer_type(cls, num: int, coordinate: str):
"""
用于确定辅助玩家以某一数据类型走指定长度
-1 -> 0
1 -> 1
[128, 127] -> 2
[-32768, 32767] -> 3
[-2147483648, 2147483647] -> 4
:param num:
:param coordinate: 坐标轴种类x y 或 z
:return:
"""
if num == 0:
return
pointer = 0
condition = (num != -1, # byte=0, pointer=1
num < -1 or num > 1, # byte=1, pointer=2
num < -128 or num > 127, # byte=2, pointer=3
num < -32768 or num > 32767, # byte=4, pointer=4
)
for i in condition:
if i:
pointer += 1
pointer_type = cls.keys[coordinate][pointer]
byte_len = 2 ** (pointer - 2)
if byte_len >= 1:
num_byte = num.to_bytes(byte_len, byteorder="big", signed=True)
return pointer_type + num_byte
return pointer_type
def obtain_universal_block(self, block):
"""
给定一个方块, 返回此方块在这个bdx中的id和方块data
:param block: {block_name: str,particular_value: int}
:return: bytes
"""
block_id = b"\x07" + self.block_type.index(block["block_name"]).to_bytes(2, byteorder="big", signed=False)
particular_value = block["particular_value"].to_bytes(2, byteorder="big", signed=False)
block_header = block_id + particular_value
return block_header
def obtain_command_block(self, block):
"""
给定一个命令方块,返回命令方块各种数据
:param block: {
"direction": [x: int, y: int, z: int]
"block_name": str,
"particular_value": int,
"impluse": int, # unsigned_int32
"command": str,
"customName": str,
"lastOutput": str, # 没特殊要求写个\x00就得了
"tickdelay": int, # int32
"executeOnFirstTick": int, # 1 bytes
"trackOutput": int, # 1 bytes
"conditional": int, # 1 bytes
"needRedstone": int # 1 bytes
}
:return: bytes of command_block
"""
block_id = b"\x1b" + self.block_type.index(block["block_name"]).to_bytes(2, byteorder="big", signed=False)
particular_value = block["particular_value"].to_bytes(2, byteorder="big", signed=False)
block_header = block_id + particular_value
for i in [
block["impluse"].to_bytes(4, byteorder="big", signed=False),
bytes(block["command"], encoding="utf-8") + b"\x00",
bytes(block["customName"], encoding="utf-8") + b"\x00",
bytes(block["lastOutput"], encoding="utf-8") + b"\x00",
block["tickdelay"].to_bytes(4, byteorder="big", signed=True),
block["executeOnFirstTick"].to_bytes(1, byteorder="big"),
block["trackOutput"].to_bytes(1, byteorder="big"),
block["conditional"].to_bytes(1, byteorder="big"),
block["needRedstone"].to_bytes(1, byteorder="big")
]:
block_header += i
return block_header
if __name__ == '__main__':
block = [{"direction": [-1, -1, -1], "block_name": "concrete", "particular_value": 5},
{"direction": [1, 5, 1], "block_name": "stained_glass", "particular_value": 7},
{"direction": [2, 4, 1], "block_name": "command_block", "particular_value": 3,
"impluse": 0,
"command": "say A generator test",
"customName": "test",
"lastOutput": "",
"tickdelay": 24,
"executeOnFirstTick": 0,
"trackOutput": 0,
"conditional": 0,
"needRedstone": 1
},
{"direction": [3, 4, 1], "block_name": "concrete", "particular_value": 6},
{"direction": [-123412133, 4, 1], "block_name": "concrete", "particular_value": 7}]
bdx = BdxConverter("./test02.bdx", "Charlie_Ping",block)

View File

@@ -0,0 +1,134 @@
# -*- coding: UTF-8 -*-
'''提供错误报告的基本操作及方法 顺便提供版本更新、安装库等功能'''
def makeZip(sourceDir, outFilename,compression = 8,exceptFile = None):
'''使用compression指定的算法打包目录为zip文件\n
默认算法为DEFLATED(8),可用算法如下:\n
STORED = 0\n
DEFLATED = 8\n
BZIP2 = 12\n
LZMA = 14\n
'''
import os, zipfile
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;
print(filename)
pathfile = os.path.join(parent, filename)
arcname = pathfile[pre_len:].strip(os.path.sep) #相对路径
zipf.write(pathfile, arcname)
zipf.close()
del zipf,pre_len
#以上函数节选并修改自 正在攀登的小蜗牛 的博客https://blog.csdn.net/qq_21127151/article/details/107503942
class report():
'''发送报告以及相应的任务处理'''
def __init__(self,senderName:str = 'Unknown',senderContact:str = 'None',describetion:str = ''):
''':param senderName 发送者名称
:param senderContact 发送者联系方式
:param describetion 问题描述'''
self.senderName = senderName;
self.senderContact = senderContact;
self.describetion = describetion;
if not self.senderName :
self.senderName = 'Unknown';
if not self.senderContact :
self.senderContact = 'None';
def emailReport(self):
'''使用E-mail方法发送当前的日志和临时文件等'''
import smtplib
from email.mime.text import MIMEText;
from email.mime.multipart import MIMEMultipart;
from email.header import Header;
from musicreater.nmcsup.log import log
log("发送错误报告")
import os;
log("添加标题与正文")
msg = MIMEMultipart();
#发送者与接收者显示名称
msg["From"] = Header(self.senderName,'utf-8');
msg["To"] = Header("W-YI (QQ2647547478)",'utf-8');
#标题
msg["Subject"] = '音·创 - 来自 '+self.senderName+' 的错误报告';
#正文
msg.attach(MIMEText("来自"+self.senderName+"( "+self.senderContact+" )的错误描述:\n"+self.describetion,'plain','utf-8'));
log("添加完毕,正在生成压缩包...")
makeZip("./","Temps&Logs.zip",exceptFile="Temps&Logs.zip");
attafile=MIMEText(open("Temps&Logs.zip",'rb').read(),"base64",'gb2312');
attafile["Content-Type"] = 'application/octet-stream';
attafile["Content-Disposition"] = 'attachment;filename="BugReport_from_'+self.senderName+'.zip"';
msg.attach(attafile);
log("完毕,准备发送")
try:
smtp = smtplib.SMTP()
smtp.connect("smtp.163.com");
#SIQQKQQYCZRVIDFJ是授权密码
smtp.login("RyounDevTeam@163.com","SIQQKQQYCZRVIDFJ");
smtp.sendmail("RyounDevTeam@163.com",["RyounDevTeam@163.com",],msg.as_string())
log("错误汇报邮件已发送")
except smtplib.SMTPException as e:
log("错误汇报邮件发送失败:\n"+str(e));
log("清空内存和临时文件")
del msg,attafile
os.remove("./Temps&Logs.zip")
class version:
libraries = ('mido','amulet','amulet-core','amulet-nbt','piano_transcription_inference','pypinyin','briefcase','toga','pyinstaller','py7zr','websockets','torch')
'''当前开发所需库'''
version = ('0.0.0','Gamma',)
'''当前版本'''
def __init__(self) -> None:
self.libraries = version.libraries
'''当前开发所需库'''
self.version = version.version
'''当前版本'''
def installLibraries(self):
'''安装全部开发用库'''
from sys import platform
import os
if platform == 'win32':
import shutil
try:
shutil.rmtree(os.getenv('APPDATA')+'\\Musicreater\\')
except:
pass;
for i in self.libraries:
print("安装库:"+i)
os.system("python -m pip install "+i+" -i https://pypi.tuna.tsinghua.edu.cn/simple")
elif platform == 'linux':
os.system("sudo apt-get install python3-pip")
os.system("sudo apt-get install python3-tkinter")
for i in self.libraries:
print("安装库:"+i)
os.system("sudo python3 -m pip install "+i+" -i https://pypi.tuna.tsinghua.edu.cn/simple")

View File

@@ -0,0 +1,215 @@
# -*- coding: utf-8 -*-
"""音·创 的函数操作和一些其他功能"""
def delPart(Data,starter,ender,includeStart :bool= True,includend :bool= True):
'''删除序列从starter物件到ender物件之间的部分\n
includeStart与inclodend分别控制此函数是否包括starter和ender物件所在部分默认为真\n
starter与ender若为None则默认从首或尾开始'''
try:
if starter == None:
includeStart = True;
starter = Data[0];
if ender == None:
includend = True;
ender = Data[len(Data)-1];
if includend:
if includeStart:
return Data[Data.index(starter):len(Data)-Data[len(Data)::-1].index(ender)];
else:
return Data[Data.index(starter)+1:len(Data)-Data[len(Data)::-1].index(ender)];
else:
if includeStart:
return Data[Data.index(starter):len(Data)-Data[len(Data)::-1].index(ender)-1];
else:
return Data[Data.index(starter)+1:len(Data)-Data[len(Data)::-1].index(ender)-1];
except:
return 0
def keepart(Data,starter,ender,includeStart :bool= True,includend :bool= True):
'''保留序列从starter物件到ender物件之间的部分\n
includeStart与inclodend分别控制此函数是否包括starter和ender物件所在部分默认为真\n
starter与ender若为None则默认从首或尾开始'''
try:
if starter == None:
includeStart = True;
starter = Data[0];
if ender == None:
includend = True;
ender = Data[len(Data)-1];
if includend:
if includeStart:
return Data[Data.index(starter):Data.index(ender)+1];
else:
return Data[Data.index(starter)+1:Data.index(ender)+1];
else:
if includeStart:
return Data[Data.index(starter):Data.index(ender)];
else:
return Data[Data.index(starter)+1:Data.index(ender)];
except:
return 0
def lenFunction(fun) -> int:
'''取得函数指令部分长度,即忽略#开头的注释'''
try:
l = 0;
for i in fun:
if i.replace(" ",'')[0] == '#':
l += 1;
return len(fun)-l;
except:
return -1;
def funSplit(bigFile,maxCmdLen : int = 10000 ):
'''分割bigFile大的函数文件bigFile需要读入文件流\n
返回的部分,每行指令皆带有行尾换行符\\n\n
返回-1为大小低于maxCmdLen最长函数指令长度'''
bigFile = bigFile.readlines()
if lenFunction(bigFile) < maxCmdLen:
return -1;
part = [];
parts = [];
l = 0;
for i in bigFile:
if i.replace(" ",'')[0] == '#':
part.append(i+'\n');
else:
part.append(i+'\n');
l += 1;
if l >= 10000:
parts.append(part)
part = [];
l = 0;
return parts;
def makeFuncFiles(musicset, path='./'):
from musicreater.nmcsup.log import log
'''在指定目录下生成函数文件'''
from musicreater.nmcsup.trans import Note2Cmd
commands = []
starts = []
log("=========================正在在此处生成文件:"+path)
maxlen = -1
for i in range(len(musicset['musics'])):
log('写入第'+str(i)+'个数据')
commands.append("scoreboard players add @e[name=\""+musicset['musics'][i]['set']['EntityName']+"\"] "+musicset['musics'][i]['set']['ScoreboardName']+" 1\n")
commands.append("execute @e[name=\""+musicset['musics'][i]['set']['EntityName'] +"\",scores={"+musicset['musics'][i]['set']['ScoreboardName']+"=1..10}] ~~~ title @a"+musicset['mainset']['PlayerSelect']+" title "+musicset['mainset']['MusicTitle']+"\n")
commands.append("execute @e[name=\""+musicset['musics'][i]['set']['EntityName'] +"\",scores={"+musicset['musics'][i]['set']['ScoreboardName']+"=1..10}] ~~~ title @a"+musicset['mainset']['PlayerSelect']+" subtitle 本函数乐曲由§b§l凌云§r§3函数音乐创建§r生成\n")
if len(musicset['musics'][i]['notes']) > maxlen:
maxlen = len(musicset['musics'][i]['notes'])
starts.append("scoreboard objectives add " +musicset['musics'][i]['set']['ScoreboardName']+" dummy\n")
starts.append("summon armor_stand " +musicset['musics'][i]['set']['EntityName']+'\n')
with open(path+musicset['mainset']['MusicTitle']+'_Part'+str(i)+'.mcfunction', 'w', encoding='UTF-8') as f:
f.writelines(Note2Cmd(musicset['musics'][i]['notes'],musicset['musics'][i]['set']['ScoreboardName'],musicset['musics'][i]['set']['Instrument'],musicset['mainset']['PlayerSelect'],True))
if musicset['mainset']['IsRepeat']:
log("增加重复语句")
for i in range(len(musicset['musics'])):
commands.append("execute @e[name=\""+musicset['musics'][i]['set']['EntityName']+"\",scores={"+musicset['musics'][i]['set']['ScoreboardName']+"="+str((maxlen+2)*10)+"}] ~~~ scoreboard players set @e[name=\""+musicset['musics'][i]['set']['EntityName']+"\"] "+musicset['musics'][i]['set']['ScoreboardName']+" -1\n")
log("增加版权语句")
commands.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿\n")
starts.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿\n")
log("写入支持文件")
with open(path+musicset['mainset']['MusicTitle']+'_Support.mcfunction', 'w', encoding='UTF-8') as f:
f.writelines(commands)
log("写入开始文件")
with open(path+'Start_'+musicset['mainset']['MusicTitle']+'.mcfunction', 'w', encoding='UTF-8') as f:
f.writelines(starts)
del commands, starts, maxlen
log("完成============================")
def makeFunDir(musicset, path='./'):
from musicreater.nmcsup.log import log
'''在指定目录下生成函数包文件夹'''
import os
import uuid
log("=============================生成函数包文件夹")
# note,packname="Ryoun",FileName="Music",EntityName_='music_support',ScoreboardName_='music_support',MusicTitle_='Noname',PlayerSelect_='',Repeat_=False,Instrument_='harp'
try:
os.makedirs(path+musicset['mainset']['PackName'] +"Pack/behavior_packs/"+musicset['mainset']['PackName']+"/functions")
log("已创建目录"+path+musicset['mainset']['PackName'] +"Pack/behavior_packs/"+musicset['mainset']['PackName']+"/functions")
except:
log("目录已有无需创建")
pass
# 判断文件皆存在
if not(os.path.exists(path+musicset['mainset']['PackName']+"Pack/world_behavior_packs.json") and os.path.exists(path+musicset['mainset']['PackName']+"Pack/behavior_packs/"+musicset['mainset']['PackName']+"/manifest.json")):
log("创建manifest.json以及world_behavior_packs.json")
behaviorUuid = uuid.uuid4()
with open(path+musicset['mainset']['PackName']+"Pack/world_behavior_packs.json", "w") as f:
f.write("[\n {\"pack_id\": \"" + str(behaviorUuid) +"\",\n \"version\": [ 0, 0, 1 ]}\n]")
with open(path+musicset['mainset']['PackName']+"Pack/behavior_packs/"+musicset['mainset']['PackName']+"/manifest.json", "w") as f:
f.write("{\n \"format_version\": 1,\n \"header\": {\n \"description\": \""+musicset['mainset']['PackName']+" Pack : behavior pack\",\n \"version\": [ 0, 0, 1 ],\n \"name\": \""+musicset['mainset']['PackName']+"Pack\",\n \"uuid\": \"" + str(behaviorUuid) + "\"\n },\n \"modules\": [\n {\n \"description\": \""+musicset['mainset']['PackName']+" Pack : behavior pack\",\n \"type\": \"data\",\n \"version\": [ 0, 0, 1 ],\n \"uuid\": \"" + str(uuid.uuid4()) + "\"\n }\n ]\n}")
makeFuncFiles(musicset, path+musicset['mainset']['PackName'] +"Pack/behavior_packs/"+musicset['mainset']['PackName']+"/functions/")
log("完成============================")
'''
这里是往事,用于记载一些用不到的功能
#存在于 Musicreater.py 播放(试听)音乐
def PlayNote(Notes, t=480): # Notes是音符列表t是一拍占有的毫秒数
tkinter.messagebox.showinfo(title='提示!', message="播放发音不一定标准\n说不定还会坏音响/(ㄒoㄒ)/~~qwq\n请注意。")
import winsound
import time
from nmcsup.trans import mcnote2freq
Notes = mcnote2freq(Notes)
for frequency, duration in Notes:
log("播放:"+str([int(frequency), int(duration*t)]))
if int(frequency) != 0:
winsound.Beep(int(frequency), int(duration*t))
elif int(frequency) == 0:
time.sleep(duration*t/1000)
#同上,执行播放命令
def PlayOne():
log("试听")
tkinter.messagebox.showwarning(title="警告⚠", message="试听音质可能引起您的不适,更可能引起您的扬声器的不适,请酌情播放。")
global NowMusic
PlayNote(dataset[0]['musics'][NowMusic]['notes'])
#同上,是早期 MinecraftMusicFunctionMaker.py (函数音创)的代码转移至音·创时的注解
n2c(dataset[0]['musics'][i]['notes'],EntityName=dataset[0]['musics'][i]['set']['EntityName'],ScoreboardName=dataset[0]['musics'][i]['set']['ScoreboardName'],PlayerSelect=dataset[0]['mainset']['PlayerSelect'],Instrument=dataset[0]['musics'][i]['set']["Instrument"])
'''

View File

@@ -0,0 +1,27 @@
import threading
class NewThread(threading.Thread):
'''新建一个进程来运行函数,函数运行完毕后可以使用.getResult方法获取其返回值'''
def __init__(self, func, args=()):
super(NewThread, self).__init__()
self.func = func
self.args = args
def run(self):
self.result = self.func(*self.args)
def getResult(self):
threading.Thread.join(self) # 等待线程执行完毕
try:
return self.result
except Exception:
return None
#
# ————————————————
# 版权声明上面的类NewThread修改自CSDN博主「星火燎愿」的原创文章中的内容遵循CC 4.0 BY-SA版权协议转载请附上原文出处链接及本声明。
# 原文链接https://blog.csdn.net/xpt211314/article/details/109543014
# ————————————————
#

View File

@@ -0,0 +1,308 @@
"""音·创 的转换工具库"""
def hans2pinyin(hans,style=3):
"""将汉字字符串转化为拼音字符串"""
from pypinyin import lazy_pinyin
result = lazy_pinyin(hans=hans,style=style)
final = ''
for i in result:
final += i;
return final
def formCmdBlock(direction:list,command:str,particularValue:int,impluse:int,condition:bool=False,needRedstone:bool=True,tickDelay:int=0,customName:str='',lastOutput:str='',executeOnFirstTick:bool=False,trackOutput:bool=True):
"""
使用指定项目返回指定的指令方块格式字典
:param block: {
"direction": [x: int, y: int, z: int] #方块位置
"block_name": str, #方块名称无需指定默认为command_block
"particular_value": int, #方块特殊值
"impluse": int, #方块类型0脉冲 1循环 2连锁 unsigned_int32
"command": str, #指令
"customName": str, #悬浮字
"lastOutput": str, #上次输出
"tickdelay": int, #方块延时 int32
"executeOnFirstTick": int, #执行第一个选项 1 bytes
"trackOutput": int, #是否输出 1 bytes
"conditional": int, #是否有条件 1 bytes
"needRedstone": int #是否需要红石 1 bytes
}
:return: 指令方块字典结构
"""
return {"direction": direction,
"block_name": "command_block",
"particular_value": particularValue,
"impluse": impluse,
"command": command,
"customName": customName,
"lastOutput": lastOutput,
"tickdelay": tickDelay,
"executeOnFirstTick": executeOnFirstTick,
"trackOutput": trackOutput,
"conditional": condition,
"needRedstone": needRedstone
}
def note2bdx(filePath:str,dire:list,Notes : list,ScoreboardName:str,Instrument:str, PlayerSelect:str='',isProsess:bool=False,height:int = 200) :
'''使用方法同Note2Cmd
:param 参数说明:
filePath: 生成.bdx文件的位置
dire: 指令方块在地图中生成的起始位置(相对位置)
Notes: 以 list[ list[ float我的世界playsound指令音调 , float延续时常单位s ] ] 格式存储的音符列表 例如Musicreater.py的(dataset[0]['musics'][NowMusic]['notes'])
ScoreboardName: 用于执行的计分板名称
Instrument: 播放的乐器
PlayerSelect: 执行的玩家选择器
isProsess: 是否显示进度条(会很卡)
height: 生成结构的最高高度
:return 返回一个BdxConverter类实际上没研究过同时在指定位置生成.bdx文件'''
from musicreater.msctspt.transfer import formCmdBlock
from musicreater.nmcsup.trans import Note2Cmd
from musicreater.msctspt.bdxOpera_CP import BdxConverter
cmd = Note2Cmd(Notes,ScoreboardName,Instrument, PlayerSelect,isProsess)
cdl = []
for i in cmd:
try:
if (i[:i.index('#')].replace(' ','') != '\n') and(i[:i.index('#')].replace(' ','') != ''):
cdl.append(i[:i.index('#')])
except:
cdl.append(i)
i = 0
down = False
blocks = [formCmdBlock(dire,cdl.pop(0),1,1)]
dire[1]+=1;
for j in cdl:
if dire[1]+i > height:
dire[0]+=1
i=0
down = not down
if dire[1]+i == height :
blocks.append(formCmdBlock([dire[0],dire[1]+i,dire[2]],j,5,2,False,False))
else:
if down:
blocks.append(formCmdBlock([dire[0],dire[1]+i,dire[2]],j,0,2,False,False))
else:
blocks.append(formCmdBlock([dire[0],dire[1]+i,dire[2]],j,1,2,False,False))
i+=1
del i, cdl, down, cmd
return BdxConverter(filePath,'Build by RyounMusicreater',blocks)
def note2webs(Notes : list,Instrument:str, speed:float = 5.0, PlayerSelect:str='',isProsess:bool=False) :
'''传入音符在oaclhost:8080上建立websocket服务器以供我的世界connect/wssever指令连接
:param 参数说明:
Notes: 以 list[ list[ float我的世界playsound指令音调 , float延续时常单位s ] ] 格式存储的音符列表 例如Musicreater.py的(dataset[0]['musics'][NowMusic]['notes'])
Instrument: 播放的乐器
speed: 用于控制播放速度,数值越大,播放速度越快,相当于把一秒变为几拍
PlayerSelect: 执行的玩家选择器
isProsess: 是否显示进度条
:return None'''
import time
import fcwslib
import asyncio
from musicreater.nmcsup.log import log
from musicreater.nmcsup.vers import VER
async def run_server(websocket, path):
log('服务器连接创建')
await fcwslib.tellraw(websocket, '已连接服务器——音·创'+VER[1]+VER[0]+' 作者:金羿(W-YI)')
if isProsess:
length = len(Notes)
j = 1;
for i in range(len(Notes)):
await fcwslib.send_command(websocket,'execute @a'+PlayerSelect+' ~ ~ ~ playsound '+Instrument+' @s ~ ~ ~ 1000 '+str(Notes[i][0])+' 1000')
if isProsess:
fcwslib.send_command(websocket,'execute @a'+PlayerSelect+' ~ ~ ~ title @s actionbar §e▶ 播放中: §a'+str(j)+'/'+str(length)+' || '+str(int(j/length*1000)/10))
j+=1;
time.sleep(Notes[i][1]/speed)
fcwslib.run_server(run_server)
import amulet
from amulet.api.block import Block
from amulet.utils.world_utils import block_coords_to_chunk_coords as bc2cc
from amulet_nbt import TAG_String as ts
from nmcsup.log import log
def note2RSworld(world:str,startpos:list,notes:list,instrument:str,speed:float = 2.5,posadder:list = [1,0,0],baseblock:str = 'stone') -> bool:
'''传入音符,生成以音符盒存储的红石音乐
:param 参数说明:
world: 地图文件的路径
startpos: list[int,int,int] 开始生成的坐标
notes: list[list[float,float]] 以 list[ list[ float我的世界playsound指令音调 , float延续时常单位s ] ] 格式存储的音符列表 例如Musicreater.py的dataset[0]['musics'][NowMusic]['notes']
instrument: 播放的乐器
speed: 一拍占多少个中继器延迟(红石刻/rt)
posadder: list[int,int,int] 坐标增加规律,即红石的延长时按照此增加规律增加坐标
baseblock: 在中继器下垫着啥方块呢~
:return 是否生成成功
'''
from musicreater.msctspt.values import height2note,instuments
def formNoteBlock(note:int,instrument:str='note.harp',powered:bool = False):
'''生成音符盒方块
:param note: 0~24
:return Block()'''
if powered:
powered = 'true';
else:
powered = 'false';
return Block('universal_minecraft','noteblock',{"instrument":ts(instrument.replace("note.",'')),'note':ts(str(note)),'powered':ts(powered)})
def formRepeater(delay:int,facing:str,locked:bool=False,powered:bool=False):
'''生成中继器方块
:param delay: 1~4
:return Block()'''
if powered:powered = 'true';
else:powered = 'false';
if locked:locked = 'true';
else:locked = 'false';
return Block('universal_minecraft','repeater',{"delay":ts(str(delay)),'facing':ts(facing),'locked':ts(locked),'powered':ts(powered)})
level = amulet.load_level(world)
def setblock(block:Block,pos:list):
'''pos : list[int,int,int]'''
cx, cz = bc2cc(pos[0], pos[2])
chunk = level.get_chunk(cx, cz, "minecraft:overworld")
offset_x, offset_z = pos[0] - 16 * cx, pos[2] - 16 * cz
chunk.blocks[offset_x, pos[1], offset_z] = level.block_palette.get_add_block(block)
chunk.changed = True
# 1拍 x 2.5 rt
def placeNoteBlock():
for i in notes:
try :
setblock(formNoteBlock(height2note[i[0]],instrument),[startpos[0],startpos[1]+1,startpos[2]])
setblock(Block("universal_minecraft",instuments[i[0]][1]),startpos)
except :
log("无法放置音符:"+str(i)+''+str(startpos))
setblock(Block("universal_minecraft",baseblock),startpos)
setblock(Block("universal_minecraft",baseblock),[startpos[0],startpos[1]+1,startpos[2]])
delay = int(i[1]*speed+0.5)
if delay <= 4:
startpos[0]+=1
setblock(formRepeater(delay,'west'),[startpos[0],startpos[1]+1,startpos[2]])
setblock(Block("universal_minecraft",baseblock),startpos)
else:
for i in range(int(delay/4)):
startpos[0]+=1
setblock(formRepeater(4,'west'),[startpos[0],startpos[1]+1,startpos[2]])
setblock(Block("universal_minecraft",baseblock),startpos)
if delay % 4 != 0:
startpos[0]+=1
setblock(formRepeater(delay%4,'west'),[startpos[0],startpos[1]+1,startpos[2]])
setblock(Block("universal_minecraft",baseblock),startpos)
startpos[0]+=posadder[0]
startpos[1]+=posadder[1]
startpos[2]+=posadder[2]
try:
placeNoteBlock()
except:
log("无法放置方块了,可能是因为区块未加载叭")
level.save()
level.close()
class ryStruct:
def __init__(self,world:str) -> None:
self.RyStruct = dict()
self._world = world
self._level = amulet.load_level(world)
def reloadLevel(self):
try:
self._level = amulet.load_level(self.world)
except:
log("无法重载地图")
def closeLevel(self):
try:
self._level.close()
except:
log("无法关闭地图")
def world2Rys(self,startp:list,endp:list,includeAir:bool=False):
'''将世界转换为RyStruct字典注意此函数运行成功后将关闭地图若要打开需要运行 reloadLevel
:param startp: [x,y,z] 转化的起始坐标
:param endp : [x,y,z] 转换的终止坐标,注意,终止坐标需要大于起始坐标,且最终结果包含终止坐标
:param includeAir : bool = False 是否包含空气,即空气是否在生成之时覆盖地图内容
:return dict RyStruct '''
level = self._level
for x in range(startp[0],endp[0]+1):
for y in range(startp[1],endp[1]+1):
for z in range(startp[2],endp[2]+1):
RyStructBlock = dict()
cx, cz = bc2cc(x, z)
chunk = level.get_chunk(cx, cz, "minecraft:overworld")
universal_block = chunk.block_palette[chunk.blocks[x - 16 * cx, y, z - 16 * cz]]
if universal_block == Block("universal_minecraft","air") and includeAir:
continue
universal_block_entity = chunk.block_entities.get((x, y, z), None)
RyStructBlock["block"] = str(universal_block)
RyStructBlock["blockEntity"] = str(universal_block_entity)
log("载入方块数据"+str(RyStructBlock))
self.RyStruct[(x,y,z)] = RyStructBlock
level.close()
return self.RyStruct
'''
RyStruct = {
(0,0,0) = {
"block": str 完整的方块结构
"blockEntity": str | 'None'
}
}
'''

View File

@@ -0,0 +1,59 @@
instuments = {
'note.banjo' : ['班卓琴','hay_block'],
'note.bass' : ['贝斯','planks'],
'note.bassattack' : ['低音鼓/贝斯','log'],
'note.bd' : ['底鼓','stone'], #即basedrum
'note.bell' : ['铃铛/钟琴','gold_block'],
'note.bit' : ['比特/“芯片”(方波)','emerald_block'],
'note.chime' : ['管钟','packed_ice'],
'note.cow_bell' : ['牛铃','soul_sand'],
'note.didgeridoo' : ['迪吉里杜管','pumpkin'],
'note.flute' : ['长笛','clay'],
'note.guitar' : ['吉他','wool'],
'note.harp' : ['竖琴/钢琴','concrete'], #任意其他类型的方块皆可
'note.hat' : ['击鼓沿/架子鼓','glass'],
'note.iron_xylophone' : ['“铁木琴”(颤音琴)','iron_block'],
'note.pling' : ['“扣弦”(电钢琴)','glowstone'],
'note.snare' : ['小军鼓','sand'],
'note.xylophone' : ['木琴','bone_block']
}
'''乐器对照表\n
乐器英文:[中文, 对应音符盒下方块名称]
注:方块仅取一个'''
height2note = {
0.5: 0,
0.53: 1,
0.56: 2,
0.6: 3,
0.63: 4,
0.67: 5,
0.7: 6,
0.75: 7,
0.8: 8,
0.84: 9,
0.9: 10,
0.94: 11,
1.0: 12,
1.05: 13,
1.12: 14,
1.2: 15,
1.25: 16,
1.33: 17,
1.4: 18,
1.5: 19,
1.6: 20,
1.7: 21,
1.8: 22,
1.9: 23,
2.0: 24,
}
'''音高对照表\n
MC音高:音符盒音调'''

View File

@@ -0,0 +1,72 @@
从此日志开始,我的世界函数音乐构建更名为 函数音创 NoteFunCreater谐音NotFun[狗头]版本号更为0.1.0开始
注意,运行此文件需要第三方库:
1. mido 用于对midi文件的解码
2. py7zr 用于对7z压缩包的压缩与解压等需pycparser, cffi, texttable, pyzstd, pyppmd, pycryptodomex, multivolumefile, brotli, bcj-cffi支持 -(从0.1.3开始不需要)
3. zipfile 用于自动生成函数包的压缩
4. pystray 用于支持窗口任务栏
5. pillow 相当于Python2的PIL用于绘图
0.1.0
2021 7 10 - 2021 7 12
1.程序窗口化
2.仅支持基本的菜单操作
3.程序文件皆储存至其相应目录下
4.程序./bin/目录下文件将会自动防修改
5.删除了彩蛋
0.1.1
2021 7 14
1.新增版本辨别的提示
2.窗口中显示歌曲信息
0.1.2
2021 7 14 - 2021 7 15
1.在没运行过的机器上会自动安装库
2.从midi导入时不会删除其他音轨
3.改进UI样式
4.支持对于单个音轨设置的修改以及音乐主设置的修改
5.当未保存便退出时,会询问存储
6.新增加载进度提示
0.1.3
2021 7 15 - 2021 7 19
1.不再从文件中读取音符及乐器信息(所以包更小了)
2.改进UI
3.修复了修改玩家选择器时变更了音乐标题的bug
4.新增删除当前选定音轨按钮
5.新增重置设置按钮(将音乐总设置设置为开始时的设置)
6.运用多线程加载函数与文件等,程序运行效率更高
7.修复变量作用域混淆问题
0.1.3.1
2021 7 19
1.修复了菜单中无法退出程序的问题
0.1.4
2021 7 22
1.支持显示指令于列表中
TO-DO
1.支持从midi文件的元信息中收取音符信息并自动生成
2.支持生成zip函数包
3.支持使用WebSocket接口自动播放已编辑的音乐
4.可以编辑多个项目
5.能够自动将一个长串的音乐分成多个函数文件
6.支持用户导入自己的乐器
7.支持汇报崩溃记录(通过邮件附件的方式)
8.支持播放字幕
9.支持任务栏角标与通知
10.将控制台版本的彩蛋移植到此版本,开启了任务栏
11.可编辑音符

View File

@@ -0,0 +1,36 @@
世界音创(NoteMapCreater)是金羿开发的一款用于生成我的世界中各类有关音乐的物件的软件
软件禁止商用,源代码始终公开,如使用未经授权的音乐经过此软件生成的任何物件侵犯了他人权利与本软件及其作者无关
Copyright © W-YI 2021
开头,特别感谢:
KCINE提供Cinemusicedit函数包(虽然函数包没怎么用过)
Charlie_Ping提供MusiCreaterBot(音乐地图生成QQ机器人)源码核心以及时不时的催更(虽然源码没有抄)
金羿(作者本人)提供NoteFunCreater(函数音创)的制作经验以及时不时的摸鱼(虽然不是很支持函数音创)
广大群友:高效的催更作业让我以蜗牛的速度前进
Alpha 0.0.0
2021 8 1 - 2021 8 10
1.确定了大概的功能
2.不支持无参数传入
3.可以查看帮助,但是帮助大多功能没实现
4.可以从格式文本、midi文件、钢琴声音MP3导入音轨
5.可以生成一些方块到世界里,但是没有播放器(半支持bw开关)
5.提供了修改文件地址的方法,但是不能修改
Alpha 0.0.1
2021 8 10
1.可以从函数音创的工程文件读取音轨
2.可以新建一个空白世界来生成
3.支持修改输出文件地址
4.支持修改输出方块起始位置
5.支持指定播放乐器,执行实体,执行积分板,播放玩家选择器
6.可以生成指令音乐地图(完全支持-w开关)
Beta 0.0.0
2021 8 X?
1.除了-nw 和 -f 开关不支持以外都支持了
Beta 0.0.1
2021 8 19
1.修复了大量bug

View File

View File

@@ -0,0 +1,320 @@
"""音创系列的音符对照表 以及一系列常数"""
notes = {
'....A' : [0.074, 27.5, 'wood', 8],
'....A#' : [0.0787, 29.135, 'wood', 9],
'....B' : [0.083, 30.868, 'wood', 10],
'...C' : [0.088, 32.703, 'wood', 11],
'...C#' : [0.094, 34.648, 'wood', 12],
'...D' : [0.1, 36.708, 'wood', 13],
'...D#' : [0.105, 38.891, 'log', 0],
'...E' : [0.11, 41.203, 'log', 1],
'...F' : [0.12, 43.654, 'log', 2],
'...F#' : [0.125, 46.249, 'wood', 0],
'...G' : [0.13, 48.999, 'wood', 1],
'...G#' : [0.14, 51.913, 'wood', 2],
'...A' : [0.15, 55.0, 'wood', 3],
'...A#' : [0.16, 58.27, 'wood', 4],
'...B' : [0.17, 61.735, 'wood', 5],
'..C' : [0.18, 65.406, 'wool', 0],
'..C#' : [0.19, 69.296, 'wool', 1],
'..D' : [0.2, 73.416, 'wool', 2],
'..D#' : [0.21, 77.782, 'wool', 3],
'..E' : [0.22, 82.407, 'wool', 4],
'..F' : [0.235, 87.307, 'wool', 5],
'..F#' : [0.25, 92.499, 'concretepowder', 0],
'..G' : [0.26, 97.999, 'concretepowder', 1],
'..G#' : [0.28, 103.826, 'concretepowder', 2],
'..A' : [0.3, 110.0, 'concretepowder', 3],
'..A#' : [0.31, 116.541, 'concretepowder', 4],
'..B' : [0.33, 123.471, 'concretepowder', 5],
'.C' : [0.35, 130.813, 'concretepowder', 6],
'.C#' : [0.37, 138.591, 'concretepowder', 7],
'.D' : [0.4, 146.832, 'concretepowder', 8],
'.D#' : [0.42, 155.563, 'concretepowder', 9],
'.E' : [0.44, 164.814, 'concretepowder', 10],
'.F' : [0.47, 174.614, 'concretepowder', 11],
'.F#' : [0.5, 184.997, 'concretepowder', 12],
'.G' : [0.53, 195.998, 'concretepowder', 13],
'.G#' : [0.56, 207.652, 'concretepowder', 14],
'.A' : [0.6, 220.0, 'concretepowder', 15],
'.A#' : [0.63, 233.082, 'concrete', 0],
'.B' : [0.67, 246.942, 'concrete', 1],
'C' : [0.7, 261.626, 'concrete', 2],
'C#' : [0.75, 277.183, 'concrete', 3],
'D' : [0.8, 293.665, 'concrete', 4],
'D#' : [0.84, 311.127, 'concrete', 5],
'E' : [0.9, 329.628, 'concrete', 6],
'F' : [0.94, 349.228, 'concrete', 7],
'F#' : [1.0, 369.994, 'concrete', 8],
'G' : [1.05, 391.995, 'concrete', 9],
'G#' : [1.12, 415.305, 'concrete', 10],
'A' : [1.2, 440.0, 'concrete', 11],
'A#' : [1.25, 466.164, 'concrete', 12],
'B' : [1.33, 493.883, 'concrete', 13],
'`C' : [1.4, 523.251, 'concrete', 14],
'`C#' : [1.5, 554.365, 'concrete', 15],
'`D' : [1.6, 587.33, 'stained_hardened_clay', 0],
'`D#' : [1.7, 622.254, 'stained_hardened_clay', 1],
'`E' : [1.8, 659.255, 'stained_hardened_clay', 2],
'`F' : [1.9, 698.456, 'stained_hardened_clay', 3],
'`F#' : [2.0, 739.989, 'stained_hardened_clay', 4],
'`G' : [2.1, 783.991, 'stained_hardened_clay', 5],
'`G#' : [2.24, 830.609, 'stained_hardened_clay', 6],
'`A' : [2.4, 880.0, 'stained_hardened_clay', 7],
'`A#' : [2.5, 932.328, 'stained_hardened_clay', 8],
'`B' : [2.67, 987.767, 'stained_hardened_clay', 9],
'``C' : [2.83, 1046.502, 'stained_hardened_clay', 10],
'``C#' : [3.0, 1108.731, 'stained_hardened_clay', 11],
'``D' : [3.17, 1174.659, 'stained_hardened_clay', 12],
'``D#' : [3.36, 1244.508, 'stained_hardened_clay', 13],
'``E' : [3.56, 1318.51, 'stained_hardened_clay', 14],
'``F' : [3.78, 1396.913, 'stained_hardened_clay', 15],
'``F#' : [4.0, 1479.978, 'white_glazed_terracotta', 0],
'``G' : [4.24, 1567.982, 'orange_glazed_terracotta', 0],
'``G#' : [4.5, 1661.219, 'magenta_glazed_terracotta', 0],
'``A' : [4.76, 1760.0, 'light_blue_glazed_terracotta', 0],
'``A#' : [5.04, 1864.655, 'yellow_glazed_terracotta', 0],
'``B' : [5.34, 1975.533, 'lime_glazed_terracotta', 0],
'```C' : [5.66, 2093.005, 'pink_glazed_terracotta', 0],
'```C#' : [6.0, 2217.461, 'gray_glazed_terracotta', 0],
'```D' : [6.35, 2349.318, 'silver_glazed_terracotta', 0],
'```D#' : [6.73, 2489.016, 'cyan_glazed_terracotta', 0],
'```E' : [7.13, 2637.02, 'purple_glazed_terracotta', 0],
'```F' : [7.55, 2793.826, 'blue_glazed_terracotta', 0],
'```F#' : [8.0, 2959.955, 'brown_glazed_terracotta', 0],
'```G' : [8.47, 3135.963, 'green_glazed_terracotta', 0],
'```G#' : [8.98, 3322.438, 'red_glazed_terracotta', 0],
'```A' : [9.51, 3520.0, 'black_glazed_terracotta', 0],
'```A#' : [10.08, 3729.31, 'stained_glass', 0],
'```B' : [10.68, 3951.066, 'stained_glass', 1],
'````C' : [11.31, 4186.009, 'stained_glass', 2],
'0' : [0.0, 0.0, 'glass', 0]
}
'''音符对照表\n
音符:[MC音调, 声音频率, 方块名称, 数据值]'''
#方块
'''
blocks = {
0.074 : ['stained_glass', 3],
0.0787 : ['stained_glass', 4],
0.083 : ['stained_glass', 5],
0.088 : ['stained_glass', 6],
0.094 : ['stained_glass', 7],
0.1 : ['stained_glass', 8],
0.105 : ['stained_glass', 9],
0.11 : ['stained_glass', 10],
0.12 : ['stained_glass', 11],
0.125 : ['stained_glass', 12],
0.13 : ['stained_glass', 13],
0.14 : ['stained_glass', 14],
0.15 : ['stained_glass', 15],
0.16 : ['wool', 0],
0.17 : ['wool', 1],
0.18 : ['wool', 2],
0.19 : ['wool', 3],
0.2 : ['wool', 4],
0.21 : ['wool', 5],
0.22 : ['wool', 6],
0.235 : ['wool', 7],
0.25 : ['concretepowder', 0],
0.26 : ['concretepowder', 1],
0.28 : ['concretepowder', 2],
0.3 : ['concretepowder', 3],
0.31 : ['concretepowder', 4],
0.33 : ['concretepowder', 5],
0.35 : ['concretepowder', 6],
0.37 : ['concretepowder', 7],
0.4 : ['concretepowder', 8],
0.42 : ['concretepowder', 9],
0.44 : ['concretepowder', 10],
0.47 : ['concretepowder', 11],
0.5 : ['concretepowder', 12],
0.53 : ['concretepowder', 13],
0.56 : ['concretepowder', 14],
0.6 : ['concretepowder', 15],
0.63 : ['concrete', 0],
0.67 : ['concrete', 1],
0.7 : ['concrete', 2],
0.75 : ['concrete', 3],
0.8 : ['concrete', 4],
0.84 : ['concrete', 5],
0.9 : ['concrete', 6],
0.94 : ['concrete', 7],
1.0 : ['concrete', 8],
1.05 : ['concrete', 9],
1.12 : ['concrete', 10],
1.2 : ['concrete', 11],
1.25 : ['concrete', 12],
1.33 : ['concrete', 13],
1.4 : ['concrete', 14],
1.5 : ['concrete', 15],
1.6 : ['stained_hardened_clay', 0],
1.7 : ['stained_hardened_clay', 1],
1.8 : ['stained_hardened_clay', 2],
1.9 : ['stained_hardened_clay', 3],
2.0 : ['stained_hardened_clay', 4],
2.1 : ['stained_hardened_clay', 5],
2.24 : ['stained_hardened_clay', 6],
2.4 : ['stained_hardened_clay', 7],
2.5 : ['stained_hardened_clay', 8],
2.67 : ['stained_hardened_clay', 9],
2.83 : ['stained_hardened_clay', 10],
3.0 : ['stained_hardened_clay', 11],
3.17 : ['stained_hardened_clay', 12],
3.36 : ['stained_hardened_clay', 13],
3.56 : ['stained_hardened_clay', 14],
3.78 : ['stained_hardened_clay', 15],
4.0 : ['stained_glass_pane', 0],
4.24 : ['stained_glass_pane', 1],
4.5 : ['stained_glass_pane', 2],
4.76 : ['stained_glass_pane', 3],
5.04 : ['stained_glass_pane', 4],
5.34 : ['stained_glass_pane', 5],
5.66 : ['stained_glass_pane', 6],
6.0 : ['stained_glass_pane', 7],
6.35 : ['stained_glass_pane', 8],
6.73 : ['stained_glass_pane', 9],
7.13 : ['stained_glass_pane', 10],
7.55 : ['stained_glass_pane', 11],
8.0 : ['stained_glass_pane', 12],
8.47 : ['stained_glass_pane', 13],
8.98 : ['stained_glass_pane', 14],
9.51 : ['stained_glass_pane', 15],
10.08 : ['stained_glass', 0],
10.68 : ['stained_glass', 1],
11.31 : ['stained_glass', 2],
0.0 : ['glass', 0]
}
#向查理平致敬!!!!!
'''
Blocks = {
0.074: 'barrel',
0.0787: 'beacon',
0.083: 'bedrock',
0.088: 'black_glazed_terracotta',
0.094: 'blast_furnace',
0.1: 'blue_glazed_terracotta',
0.105: 'blue_ice',
0.11: 'bone_block',
0.12: 'bookshelf',
0.125: 'brick_block',
0.13: 'brown_glazed_terracotta',
0.14: 'cartography_table',
0.15: 'carved_pumpkin',
0.16: 'clay',
0.17: 'coal_block',
0.18: 'coal_ore',
0.19: 'cobblestone',
0.2: 'concrete',
0.21: 'crafting_table',
0.22: 'cyan_glazed_terracotta',
0.235: 'diamond_block',
0.25: 'diamond_ore',
0.26: 'white_glazed_terracotta',
0.28: 'dispenser',
0.3: 'dried_kelp_block',
0.31: 'dropper',
0.33: 'emerald_block',
0.35: 'emerald_ore',
0.37: 'end_bricks',
0.4: 'end_stone',
0.42: 'fletching_table',
0.44: 'furnace',
0.47: 'glass',
0.5: 'glowingobsidian',
0.53: 'glowstone',
0.56: 'gold_block',
0.6: 'gold_ore',
0.63: 'grass',
0.67: 'gray_glazed_terracotta',
0.7: 'green_glazed_terracotta',
0.75: 'hardened_clay',
0.8: 'hay_block',
0.84: 'iron_block',
0.9: 'iron_ore',
0.94: 'jukebox',
1.0: 'lapis_block',
1.05: 'lapis_ore',
1.12: 'light_blue_glazed_terracotta',
1.2: 'lime_glazed_terracotta',
1.25: 'lit_pumpkin',
1.33: 'log',
1.4: 'loom',
1.5: 'magenta_glazed_terracotta',
1.6: 'magma',
1.7: 'melon_block',
1.8: 'web',
1.9: 'mossy_cobblestone',
2.0: 'nether_brick',
2.1: 'nether_wart_block',
2.24: 'netherrack',
2.4: 'noteblock',
2.5: 'observer',
2.67: 'obsidian',
2.83: 'orange_glazed_terracotta',
3.0: 'pink_glazed_terracotta',
3.17: 'piston',
3.36: 'planks',
3.56: 'prismarine',
3.78: 'pumpkin',
4.0: 'purple_glazed_terracotta',
4.24: 'purpur_block',
4.5: 'quartz_block',
4.76: 'quartz_ore',
5.04: 'red_glazed_terracotta',
5.34: 'red_nether_brick',
5.66: 'red_sandstone',
6.0: 'redstone_block',
6.35: 'yellow_glazed_terracotta',
6.73: 'sandstone',
7.13: 'stonebrick',
7.55: 'silver_glazed_terracotta',
8.0: 'slime',
8.47: 'smithing_table',
8.98: 'smoker',
9.51: 'smooth_stone',
10.08: 'snow',
10.68: 'soul_sand',
11.31: 'sponge',
0.0: 'stone'
}
'''频率对照表\n
MC音调:方块名称'''
# 乐器
Instuments = {
'note.banjo' : '班卓',
'note.bass' : '低音',
'note.bassattack' : '贝斯',
'note.bd' : '鼓声',
'note.bell' : '铃声',
'note.bit' : '比特',
'note.cow_bell' : '牛铃',
'note.didgeridoo' : '迪吉',
'note.flute' : '长笛',
'note.guitar' : '吉他',
'note.harp' : '竖琴',
'note.hat' : '架鼓',
'note.chime' : '钟声',
'note.iron_xylophone' : '铁琴',
'note.pling' : '叮叮',
'note.snare' : '响弦',
'note.xylophone' : '木琴'
}
'''乐器对照表\n
乐器英文:中文
翻译雪莹工坊Fun-Fer'''

View File

@@ -0,0 +1,17 @@
"""提供对于音创系列的日志"""
import datetime,os
#载入日志功能
StrStartTime = str(datetime.datetime.now()).replace(':', '_')[:-7]
'''字符串型的程序开始时间'''
def log(info:str = '',isPrinted:bool = True):
'''将信息连同当前时间载入日志'''
if not os.path.exists('./log/'):
os.makedirs('./log/')
with open('./log/'+StrStartTime+'.msct.log', 'a',encoding='UTF-8') as f:
f.write(str(datetime.datetime.now())[11:19]+' '+info+'\n')
if isPrinted:
print(str(datetime.datetime.now())[11:19]+' '+info)

View File

@@ -0,0 +1,87 @@
"""音创系列的文件读取功能"""
from musicreater.nmcsup.log import log
from musicreater.nmcsup.const import notes
#从格式文本文件读入一个音轨并存入一个列表
def ReadFile(fn : str) -> list:
from musicreater.nmcsup.trans import note2list
log('打开'+fn+"并读取音符")
try:
nat = open(fn, 'r', encoding='UTF-8').read().split(" ")
del fn
except:
log("找不到读取目标文件")
return False
Notes = []
log(str(nat)+"已读取")
for i in range(int(len(nat)/2)):
Notes.append([nat[i*2], float(nat[i*2+1])])
Notes = note2list(Notes)
log('音符数据更新'+str(Notes))
return [Notes,]
#从midi读入多个音轨返回多个音轨列表
def ReadMidi(midfile : str ) -> list:
import mido
from musicreater.msctspt.threadOpera import NewThread
Notes = []
try:
mid = mido.MidiFile(midfile)
except:
log("找不到文件或无法读取文件"+midfile)
return False
# 解析
ks = list(notes.values())
def loadMidi(track):
datas = []
for i in track:
if i.is_meta:
log('元信息'+str(i))
pass # 不处理元信息
elif 'note_on' in str(i):
msg = str(i).replace("note=", '').replace("time=", '').split(" ")
log('音符on消息处理后'+str(msg))
if msg[4] == '0':
datas.append([ks[int(msg[2])-20][0], 1.0])
log('延续时间0tick--:添加音符'+str([ks[int(msg[2])-20][0], 1.0]))
else:
datas.append([ks[int(msg[2])-20][0], float(msg[4])/480])
log('延续时间'+msg[4]+'tick--:添加音符' +str([ks[int(msg[2])-20][0], float(msg[4])/480]))
del msg
log('音符增加'+str(datas))
return datas
for j, track in enumerate(mid.tracks):
th = NewThread(loadMidi,(track,))
th.start()
Notes.append(th.getResult())
del ks
return Notes
def ReadOldProject(fn:str) -> list:
import json
from musicreater.nmcsup.trans import note2list
log("读取文件:"+fn)
try:
with open(fn, 'r', encoding='UTF-8') as c:
dataset = json.load(c)
except:
print('找不到文件:'+fn+",请查看您是否输入正确")
log("丢失"+fn)
return False
for i in range(len(dataset['musics'])):
dataset['musics'][i]['notes'] = note2list(dataset['musics'][i]['notes'])
#返回 音轨列表 选择器
return dataset

View File

@@ -0,0 +1,254 @@
"""音创系列的转换功能"""
from nmcsup.log import log
# 输入一个列表 [ [str, float ], [], ... ] 音符str 值为持续时间float
def note2list(Notes : list) -> list:
from musicreater.nmcsup.const import notes
def change(base):
enwo = {
'a': 'A',
'b': 'B',
'c': 'C',
'd': "D",
"e": "E",
'f': 'F',
'g': "G"
}
nuwo = {
'6': 'A',
'7': 'B',
'1': 'C',
'2': "D",
"3": "E",
'4': 'F',
'5': "G"
}
for k, v in enwo.items():
if k in base:
base = base.replace(k, v)
for k, v in nuwo.items():
if k in base:
base = base.replace(k, v)
return base
res = []
log(" === 音符列表=>音调列表")
for i in Notes:
s2 = change(i[0])
log(' === 正在操作音符'+i[0]+'->'+s2)
if s2 in notes.keys():
log(" === 找到此音符,加入:"+str(notes[s2][0]))
res.append([notes[s2][0], float(i[1])])
else:
log(' === '+s2+'不在音符表内,此处自动替换为 休止符0 ')
res.append(['0', float(i[1])])
log(' === 最终反回'+str(res))
return res
def mcnote2freq(Notes):
from musicreater.nmcsup.const import notes
mcnback = {}
for i,j in notes.items():
mcnback[j[0]] = i
res = []
log(" === 我的世界音调表=>频率列表")
for i in Notes:
log(' === 正在操作音符'+i[0]+'->'+mcnback[i[0]])
res.append([notes[mcnback[i[0]]][1], float(i[1])])
log(' === 最终反回'+str(res))
return res
#MP3文件转midi文件
def Mp32Mid(mp3File, midFile):
from piano_transcription_inference import PianoTranscription, sample_rate, load_audio
# 加载
(audio, _) = load_audio(mp3File, sr=sample_rate, mono=True)
# 实例化并转换
PianoTranscription(device="cpu").transcribe(audio, midFile)
#传入一个音符列表转为指令列表
def Note2Cmd(Notes : list,ScoreboardName:str,Instrument:str, PlayerSelect:str='',isProsess:bool=False) -> list:
commands = []
a = 0.0
if isProsess:
length = len(Notes)
j = 1
for i in range(len(Notes)):
commands.append("execute @a"+PlayerSelect+" ~ ~ ~ execute @s[scores={"+ScoreboardName+"="+str(int((a+2)*5+int(Notes[i][1]*5)))+"}] ~ ~ ~ playsound "+Instrument+" @s ~ ~ ~ 1000 "+str(Notes[i][0])+" 1000\n")
a += Notes[i][1]
if isProsess:
commands.append("execute @a"+PlayerSelect+" ~ ~ ~ execute @s[scores={"+ScoreboardName+"="+str(int((a+2)*5+int(Notes[i][1]*5)))+"}] ~ ~ ~ title @s actionbar §e▶ 播放中: §a"+str(j)+"/"+str(length)+" || "+str(int(j/length*1000)/10)+"\n")
j+=1
commands.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿\n")
return commands
import amulet
import amulet_nbt
from amulet.api.block import Block
from amulet.api.block_entity import BlockEntity
from amulet.utils.world_utils import block_coords_to_chunk_coords
from amulet_nbt import TAG_String,TAG_Compound,TAG_Byte
#简单载入方块
#level.set_version_block(posx,posy,posz,"minecraft:overworld",("bedrock", (1, 16, 20)),Block(namespace, name))
#转入指令列表与位置信息转至世界
def Cmd2World(cmd:list,world:str,dire:list):
'''将指令以命令链的形式载入世界\n
cmd指令列表位为一个序列中包含指令字符串\n
world为地图所在位置需要指向文件夹dire为指令方块生成之位置'''
level = amulet.load_level(world)
cdl = []
for i in cmd:
try:
if (i[:i.index('#')].replace(' ','') != '\n') and(i[:i.index('#')].replace(' ','') != ''):
cdl.append(i[:i.index('#')])
except:
cdl.append(i)
i = 0
#第一个是特殊
universal_block = Block('universal_minecraft','command_block',{'conditional':TAG_String("false"),'facing':TAG_String('up'),'mode':TAG_String("repeating")})
cx, cz = block_coords_to_chunk_coords(dire[0], dire[2])
chunk = level.get_chunk(cx, cz, "minecraft:overworld")
offset_x, offset_z = dire[0] - 16 * cx, dire[2] - 16 * cz
universal_block_entity = BlockEntity( 'universal_minecraft','command_block',dire[0],dire[1],dire[2],amulet_nbt.NBTFile(TAG_Compound({'utags': TAG_Compound({'auto': TAG_Byte(0),'Command': TAG_String(cdl.pop(0))}) })))
chunk.blocks[offset_x, dire[1], offset_z] = level.block_palette.get_add_block(universal_block)
chunk.block_entities[(dire[0], dire[1], dire[2])] = universal_block_entity
chunk.changed = True
#集体上移
dire[1]+=1;
#真正开始
down = False
for j in cdl:
if dire[1]+i >= 255:
dire[0]+=1
i=0
down = not down
#定义此方块
if dire[1]+i == 254 :
universal_block = Block('universal_minecraft','command_block',{'conditional':TAG_String("false"),'facing':TAG_String('east'),'mode':TAG_String("chain")})
else:
if down:
universal_block = Block('universal_minecraft','command_block',{'conditional':TAG_String("false"),'facing':TAG_String('down'),'mode':TAG_String("chain")})
else:
universal_block = Block('universal_minecraft','command_block',{'conditional':TAG_String("false"),'facing':TAG_String('up'),'mode':TAG_String("chain")})
cx, cz = block_coords_to_chunk_coords(dire[0], dire[2])
#获取区块
chunk = level.get_chunk(cx, cz, "minecraft:overworld")
offset_x, offset_z = dire[0] - 16 * cx, dire[2] - 16 * cz
if down:
#定义方块实体
universal_block_entity = BlockEntity( 'universal_minecraft','command_block',dire[0],254-i,dire[2],amulet_nbt.NBTFile(TAG_Compound({'utags': TAG_Compound({'auto': TAG_Byte(1),'Command': TAG_String(j)}) })))
#将方块加入世界
chunk.blocks[offset_x, 254-i, offset_z] = level.block_palette.get_add_block(universal_block)
chunk.block_entities[(dire[0], 254-i, dire[2])] = universal_block_entity
else:
#定义方块实体
universal_block_entity = BlockEntity( 'universal_minecraft','command_block',dire[0],dire[1]+i,dire[2],amulet_nbt.NBTFile(TAG_Compound({'utags': TAG_Compound({'auto': TAG_Byte(1),'Command': TAG_String(j)}) })))
#将方块加入世界
chunk.blocks[offset_x, dire[1]+i, offset_z] = level.block_palette.get_add_block(universal_block)
chunk.block_entities[(dire[0], dire[1]+i, dire[2])] = universal_block_entity
#设置为已更新区块
chunk.changed = True
i+=1
del i, cdl
#保存世界并退出
level.save()
level.close()
#音符转成方块再加载到世界里头
def Blocks2World(world:str,dire:list,Datas:list):
from musicreater.nmcsup.const import Blocks
level = amulet.load_level(world)
i = 0
def setblock(block:str,pos:list):
'''pos : list[int,int,int]'''
cx, cz = block_coords_to_chunk_coords(pos[0], pos[2])
chunk = level.get_chunk(cx, cz, "minecraft:overworld")
offset_x, offset_z = pos[0] - 16 * cx, pos[2] - 16 * cz
chunk.blocks[offset_x, pos[1], offset_z] = level.block_palette.get_add_block(Block("minecraft",block))
chunk.changed = True
for j in Datas:
if dire[1]+1 >= 255:
i = 0
dire[0]+=1
setblock(Blocks[j[0]],[dire[0],dire[1]+i,dire[2]])
i = int(i+j[1]+0.5) #四舍五入
level.save()
level.close()
#传入音符列表制作播放器指令
def Notes2Player(Note,dire:list,CmdData:dict):
'''传入音符列表、坐标、指令数据,生成播放器指令'''
Notes = {}
for i in Note:
Notes[i[0]] = ''
Notes = list(Notes.keys())
from musicreater.nmcsup.const import Blocks
Cmds = []
for j in Notes:
Cmds.append('execute @e[x='+str(dire[0])+',y='+str(dire[1])+',z='+str(dire[2])+',dy='+str(255-dire[1])+',name='+CmdData['Ent']+'] ~ ~ ~ detect ~ ~ ~ '+Blocks[j]+' 0 execute @a '+CmdData['Pls']+' ~ ~ ~ playsound '+CmdData['Ins']+' @s ~ ~ ~ 1000 '+str(j)+' 1000\n')
Cmds+=['#本函数由 金羿 音·创 生成\n','execute @e[y='+str(dire[1])+',dy='+str(255-dire[1])+',name='+CmdData['Ent']+'] ~ ~ ~ tp ~ ~1 ~\n','execute @e[y=255,dy=100,name='+CmdData['Ent']+'] ~ ~ ~ tp ~1 '+str(dire[1])+' ~\n','#音·创 开发交流群 861684859']
return Cmds
#传入音符列表生成方块至世界
def Datas2BlkWorld(NoteData,world:str,dire:list):
for i in range(len(NoteData)):
Blocks2World(world,[dire[0],dire[1],dire[2]+i],NoteData[i])

View File

@@ -0,0 +1,89 @@
"""音创系列版本号和版本操作函数"""
from musicreater.msctspt.bugReporter import version
#以下下两个值请在 msctspt/bugReporter 的version类中修改
VER = version.version
'''当前版本'''
LIBS = version.libraries
'''当前所需库'''
#判断版本、临时文件与补全库
def compver(ver1, ver2):
"""
传入不带英文的版本号,特殊情况:"10.12.2.6.5">"10.12.2.6"
:param ver1: 版本号1
:param ver2: 版本号2
:return: ver1< = >ver2返回-1/0/1
"""
list1 = str(ver1).split(".")
list2 = str(ver2).split(".")
# 循环次数为短的列表的len
for i in range(len(list1)) if len(list1) < len(list2) else range(len(list2)):
if int(list1[i]) == int(list2[i]):
pass
elif int(list1[i]) < int(list2[i]):
return -1
else:
return 1
# 循环结束,哪个列表长哪个版本号高
if len(list1) == len(list2):
return 0
elif len(list1) < len(list2):
return -1
else:
return 1
#
# ————————————————
# 版权声明上面的函数compver为CSDN博主「基友死得早」的原创文章中的函数遵循CC 4.0 BY-SA版权协议转载请附上原文出处链接及本声明。
# 原文链接https://blog.csdn.net/tinyjm/article/details/93514261
# ————————————————
#
import os
def InstallLibs(now,LIBS):
'''比对库信息并安装库'''
from os import system as run
for i in LIBS:
if not i in now:
print("安装库:"+i)
run("python -m pip install "+i+" -i https://pypi.tuna.tsinghua.edu.cn/simple")
def chkver(ver = VER,libs = LIBS):
'''通过文件比对版本信息并安装库'''
if not os.path.exists(os.getenv('APPDATA')+'\\Musicreater\\msct.ActiveDatas.msct'):
print("新安装库")
os.makedirs(os.getenv('APPDATA')+'\\Musicreater\\')
with open(os.getenv('APPDATA')+'\\Musicreater\\msct.ActiveDatas.msct', 'w') as f:
f.write(ver[0]+'\n')
for i in libs:
f.write(i+'\n')
InstallLibs([],libs)
else:
with open(os.getenv('APPDATA')+'\\Musicreater\\msct.ActiveDatas.msct', 'r') as f:
v = f.readlines()
cp = compver(ver[0], v[0])
if cp != 0:
InstallLibs(v[1:],libs)
with open(os.getenv('APPDATA')+'\\Musicreater\\msct.ActiveDatas.msct', 'w') as f:
f.write(ver[0]+'\n')
for i in libs:
f.write(i+'\n')
del cp
def resetver():
'''重置版本信息'''
import shutil
shutil.rmtree(os.getenv('APPDATA')+'\\Musicreater\\')

View File

@@ -0,0 +1,11 @@
LANGUAGE = {
'main':{
"name":"音·创",
"version":"当前版本",
"run":"执行指令",
},
'command':{
"NotAvailable":"此指令不可用。",
"FormatError":"指令格式错误,请查看 命令行操作.md 以查阅指令。"
}
}

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

View File

@@ -0,0 +1,11 @@
生命灵动 当用激情跃起奋发之力
奇偶数阵
学海无涯 应用爱意徜徉
在生命的起源寻找灵魂的慰藉
纪念那一段辉煌灿烂的青春年华
以梦想为驱使 创造属于自己的未来
集青春之力 绽放爱意之花
那个曾与我相伴的人 依稀在我的心头留恋
我爱你 我 爱 你
你是我灵魂中绽放出最艳丽的花朵
心之所向 意之所属

View File

@@ -1,3 +0,0 @@
python setup.py sdist bdist_wheel
python -m twine upload dist/*
python clean_update.py

34
命令行操作.md Normal file
View File

@@ -0,0 +1,34 @@
# Musicreater 音·创 命令行模式
为了对后期安卓的兼容,先支持命令行
## 命令列表
### exit \[/s\] \[/c\]
- ```/s``` 保存并退出
- ```/c``` 清除日志
### save \[/a \<fileName\>\]
- ```/a <文件名>``` 指定文件名保存
### load \[/\<loadMethod: input|txt|mid\> \<text|fileName\>\]
- ```/input <文本>``` 输入音符
- ```/txt <文件名>``` 从文本载入音符
- ```/mid <文件名>``` 解析midi文件载入音符
### win
- 开启窗口模式的 音·创(仅支持的系统下有效)
### chdir \<direction\>
- 切换当前工作目录
### build \[/\<buildMethod: file|directory|mcpack\> \<direction\>\]
- ```/file <目录位置>``` 在指定目录生成函数文件(散装)
- ```/directory <目录位置>``` 在指定目录生成一个附加包目录
- ```/mcpack <目录位置>``` 在指定目录生成一个.mcpack附加包

29
查看代码数.py Normal file
View File

@@ -0,0 +1,29 @@
# -*- conding: utf8 -*-
import os
from src.musicreater.msctspt.funcOpera import keepart
l = 0
for path,dir_list,file_list in os.walk(r"./") :
for file_name in file_list:
if keepart(file_name,'.',None) == '.py':
file = os.path.join(path, file_name)
print("得到文件名:"+str(file))
for i in open(file,'r',encoding="utf-8"):
code = i.replace(' ','').replace('\n','')
try:
code -= code[code.index('#'):]
except:
pass;
if code:
print("\t"+code)
l+=1
else:
pass;
input("\n最终代码行数为:"+str(l))

View File

@@ -0,0 +1,224 @@
{
"mainset": {
"PackName": "RyounTeamSong",
"MusicTitle": "RyounUp",
"IsRepeat": false,
"PlayerSelect": ""
},
"musics": [
{
"set": {
"EntityName": "MusicSupport",
"ScoreboardName": "MusicSupport",
"Instrument": "harp",
"FileName": "RyounUp_RyounTeamSong"
},
"notes": [
[
".5",
1.0
],
[
"1",
1.0
],
[
"1",
1.0
],
[
"3",
1.5
],
[
"3",
0.5
],
[
"2",
1.5
],
[
"1",
0.5
],
[
"2",
1.0
],
[
"5",
1.0
],
[
"3",
1.5
],
[
"2",
0.5
],
[
".6",
1.0
],
[
".5",
1.0
],
[
"3",
1.5
],
[
"2",
0.5
],
[
"1",
1.0
],
[
"5",
1.0
],
[
"5",
1.0
],
[
"6",
0.5
],
[
"5",
0.5
],
[
"3",
1.0
],
[
"5",
0.5
],
[
"3",
0.5
],
[
"2",
1.0
],
[
"2",
1.0
],
[
"3",
0.5
],
[
"2",
0.5
],
[
"1",
1.0
],
[
"1",
1.0
],
[
"3",
1.0
],
[
"3",
1.0
],
[
"5",
1.0
],
[
"0",
1.0
],
[
".5",
0.5
],
[
".6",
0.5
],
[
"1",
1.0
],
[
"1",
1.0
],
[
"6",
1.0
],
[
"1",
1.0
],
[
"2",
1.0
],
[
"3",
1.0
],
[
"3",
1.0
],
[
"3",
1.0
],
[
"3",
1.0
],
[
"2",
1.0
],
[
"5",
1.5
],
[
".5",
0.5
],
[
"1",
1.0
],
[
"1",
1.0
],
[
"0",
1.0
],
[
"0",
1.0
]
]
}
]
}

Binary file not shown.

File diff suppressed because one or more lines are too long